From aa910e2c67b370f80decad181debf34858ab4ce6 Mon Sep 17 00:00:00 2001 From: Xiaoji Chen Date: Sun, 16 Jan 2022 23:59:23 -0800 Subject: [PATCH] Add geocoder example (#1690) --- examples/geocoder/README.md | 12 ++ examples/geocoder/index.html | 41 ++++++ examples/geocoder/package.json | 24 ++++ examples/geocoder/src/app.tsx | 31 +++++ examples/geocoder/src/control-panel.tsx | 19 +++ examples/geocoder/src/geocoder-control.tsx | 142 +++++++++++++++++++++ examples/geocoder/tsconfig.json | 10 ++ examples/geocoder/webpack.config.js | 54 ++++++++ 8 files changed, 333 insertions(+) create mode 100644 examples/geocoder/README.md create mode 100644 examples/geocoder/index.html create mode 100644 examples/geocoder/package.json create mode 100644 examples/geocoder/src/app.tsx create mode 100644 examples/geocoder/src/control-panel.tsx create mode 100644 examples/geocoder/src/geocoder-control.tsx create mode 100644 examples/geocoder/tsconfig.json create mode 100644 examples/geocoder/webpack.config.js diff --git a/examples/geocoder/README.md b/examples/geocoder/README.md new file mode 100644 index 00000000..217b9558 --- /dev/null +++ b/examples/geocoder/README.md @@ -0,0 +1,12 @@ +# Example: Geocoder + +This app reproduces Mapbox's [Add a geocoder](https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-geocoder/) example. + +## Usage + +To run this example, you need a [Mapbox token](http://visgl.github.io/react-map-gl/docs/get-started/mapbox-tokens). You can either set it as `MAPBOX_TOKEN` in `src/app.js`, or set a `MapboxAccessToken` environment variable in the command line. + +```bash +npm i +npm run start +``` diff --git a/examples/geocoder/index.html b/examples/geocoder/index.html new file mode 100644 index 00000000..1bff7202 --- /dev/null +++ b/examples/geocoder/index.html @@ -0,0 +1,41 @@ + + + + + react-map-gl Example + + + + + +
+ + + + diff --git a/examples/geocoder/package.json b/examples/geocoder/package.json new file mode 100644 index 00000000..27c44775 --- /dev/null +++ b/examples/geocoder/package.json @@ -0,0 +1,24 @@ +{ + "scripts": { + "start": "webpack-dev-server --progress --hot --open", + "start-local": "webpack-dev-server --env local --progress --hot --open" + }, + "dependencies": { + "@mapbox/mapbox-gl-geocoder": "^4.7.4", + "react": "^17.0.0", + "react-dom": "^17.0.0", + "react-map-gl": "^7.0.0-alpha.2", + "mapbox-gl": "^2.0.0" + }, + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "babel-loader": "^8.0.0", + "ts-loader": "^9.0.0", + "typescript": "^4.0.0", + "webpack": "^5.65.0", + "webpack-cli": "^4.9.0", + "webpack-dev-server": "^4.7.0" + } +} diff --git a/examples/geocoder/src/app.tsx b/examples/geocoder/src/app.tsx new file mode 100644 index 00000000..b1b668f4 --- /dev/null +++ b/examples/geocoder/src/app.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; +import {render} from 'react-dom'; +import Map from 'react-map-gl'; + +import GeocoderControl from './geocoder-control'; +import ControlPanel from './control-panel'; + +const TOKEN = process.env.MapboxAccessToken; // Set your mapbox token here + +export default function App() { + return ( + <> + + + + + + ); +} + +export function renderToDom(container) { + render(, container); +} diff --git a/examples/geocoder/src/control-panel.tsx b/examples/geocoder/src/control-panel.tsx new file mode 100644 index 00000000..83fbceb4 --- /dev/null +++ b/examples/geocoder/src/control-panel.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; + +function ControlPanel(props) { + return ( +
+

Geocoder

+
+ + View Code ↗ + +
+
+ ); +} + +export default React.memo(ControlPanel); diff --git a/examples/geocoder/src/geocoder-control.tsx b/examples/geocoder/src/geocoder-control.tsx new file mode 100644 index 00000000..d846c8c3 --- /dev/null +++ b/examples/geocoder/src/geocoder-control.tsx @@ -0,0 +1,142 @@ +import * as React from 'react'; +import {useState} from 'react'; +import {useControl, Marker, ControlPosition} from 'react-map-gl'; +import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'; + +type GeocoderControlProps = { + mapboxAccessToken: string; + origin?: string; + zoom?: number; + flyTo?: boolean | object; + placeholder?: string; + proximity?: { + longitude: number; + latitude: number; + }; + trackProximity?: boolean; + collapsed?: boolean; + clearAndBlurOnEsc?: boolean; + clearOnBlur?: boolean; + box?: [number, number, number, number]; + countries?: string; + types?: string; + minLength?: number; + limit?: number; + language?: string; + filter?: (feature: object) => boolean; + localGeocoder?: Function; + externalGeocoder?: Function; + reverseMode?: 'distance' | 'score'; + reverseGeocode?: boolean; + enableEventLogging?: boolean; + marker?: boolean | object; + render?: (feature: object) => string; + getItemValue?: (feature: object) => string; + mode?: 'mapbox.places' | 'mapbox.places-permanent'; + localGeocoderOnly?: boolean; + autocomplete?: boolean; + fuzzyMatch?: boolean; + routing?: boolean; + worldview?: string; + + position: ControlPosition; + + onLoading?: (e: object) => void; + onResults?: (e: object) => void; + onResult?: (e: object) => void; + onError?: (e: object) => void; +}; + +export default function GeocoderControl(props: GeocoderControlProps) { + const [marker, setMarker] = useState(null); + + const geocoder: any = useControl( + () => { + const ctrl = new MapboxGeocoder({ + ...props, + accessToken: props.mapboxAccessToken + }); + ctrl.on('loading', props.onLoading); + ctrl.on('results', props.onResults); + ctrl.on('result', evt => { + props.onResult(evt); + + const {result} = evt; + const location = + result && + (result.center || (result.geometry?.type === 'Point' && result.geometry.coordinates)); + if (location) { + setMarker(); + } else { + setMarker(null); + } + }); + ctrl.on('error', props.onError); + return ctrl; + }, + { + position: props.position + } + ); + + if (geocoder._map) { + if (geocoder.getProximity() !== props.proximity && props.proximity !== undefined) { + geocoder.setProximity(props.proximity); + } + if (geocoder.getRenderFunction() !== props.render && props.render !== undefined) { + geocoder.setRenderFunction(props.render); + } + if (geocoder.getLanguage() !== props.language && props.language !== undefined) { + geocoder.setLanguage(props.language); + } + if (geocoder.getZoom() !== props.zoom && props.zoom !== undefined) { + geocoder.setZoom(props.zoom); + } + if (geocoder.getFlyTo() !== props.flyTo && props.flyTo !== undefined) { + geocoder.setFlyTo(props.zoom); + } + if (geocoder.getPlaceholder() !== props.placeholder && props.placeholder !== undefined) { + geocoder.setPlaceholder(props.zoom); + } + if (geocoder.getCountries() !== props.countries && props.countries !== undefined) { + geocoder.setCountries(props.zoom); + } + if (geocoder.getTypes() !== props.types && props.types !== undefined) { + geocoder.setTypes(props.zoom); + } + if (geocoder.getMinLength() !== props.minLength && props.minLength !== undefined) { + geocoder.setMinLength(props.zoom); + } + if (geocoder.getLimit() !== props.limit && props.limit !== undefined) { + geocoder.setLimit(props.zoom); + } + if (geocoder.getFilter() !== props.filter && props.filter !== undefined) { + geocoder.setFilter(props.zoom); + } + if (geocoder.getOrigin() !== props.origin && props.origin !== undefined) { + geocoder.setOrigin(props.zoom); + } + if (geocoder.getAutocomplete() !== props.autocomplete && props.autocomplete !== undefined) { + geocoder.setAutocomplete(props.zoom); + } + if (geocoder.getFuzzyMatch() !== props.fuzzyMatch && props.fuzzyMatch !== undefined) { + geocoder.setFuzzyMatch(props.zoom); + } + if (geocoder.getRouting() !== props.routing && props.routing !== undefined) { + geocoder.setRouting(props.zoom); + } + if (geocoder.getWorldview() !== props.worldview && props.worldview !== undefined) { + geocoder.setWorldview(props.zoom); + } + } + return marker; +} + +function noop() {} + +GeocoderControl.defaultProps = { + onLoading: noop, + onResults: noop, + onResult: noop, + onError: noop +}; diff --git a/examples/geocoder/tsconfig.json b/examples/geocoder/tsconfig.json new file mode 100644 index 00000000..ff49de04 --- /dev/null +++ b/examples/geocoder/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "es2020", + "jsx": "react", + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "moduleResolution": "node", + "sourceMap": true + } +} \ No newline at end of file diff --git a/examples/geocoder/webpack.config.js b/examples/geocoder/webpack.config.js new file mode 100644 index 00000000..ed4df0d5 --- /dev/null +++ b/examples/geocoder/webpack.config.js @@ -0,0 +1,54 @@ +// NOTE: To use this example standalone (e.g. outside of repo) +// delete the local development overrides at the bottom of this file + +// avoid destructuring for older Node version support +const resolve = require('path').resolve; +const webpack = require('webpack'); + +const config = { + mode: 'development', + + devServer: { + static: '.' + }, + + entry: { + app: resolve('./src/app') + }, + + output: { + library: 'App' + }, + + resolve: { + extensions: ['.ts', '.tsx', '.js', '.json'] + }, + + module: { + rules: [ + { + test: /\.(ts|js)x?$/, + include: [resolve('.')], + exclude: [/node_modules/], + use: [ + { + loader: 'babel-loader', + options: { + presets: ['@babel/env', '@babel/react'] + } + }, + { + loader: 'ts-loader' + } + ] + } + ] + }, + + // Optional: Enables reading mapbox token from environment variable + plugins: [new webpack.EnvironmentPlugin({MapboxAccessToken: ''})] +}; + +// Enables bundling against src in this repo rather than the installed version +module.exports = env => + env && env.local ? require('../webpack.config.local')(config)(env) : config;