├── docs ├── contributing.md ├── .gitignore ├── api-reference │ └── map-provider.md └── table-of-contents.json ├── website ├── .eslintignore ├── .gitignore ├── src │ ├── components │ │ ├── common.jsx │ │ ├── index.js │ │ ├── home │ │ │ └── index.jsx │ │ └── example │ │ │ ├── doc-item-component.jsx │ │ │ ├── examples-index.jsx │ │ │ └── styled.js │ ├── examples │ │ ├── clusters.mdx │ │ ├── geocoder.mdx │ │ ├── geojson.mdx │ │ ├── heatmap.mdx │ │ ├── terrain.mdx │ │ ├── layers.mdx │ │ ├── filter.mdx │ │ ├── custom-cursor.mdx │ │ ├── draw-polygon.mdx │ │ ├── side-by-side.mdx │ │ ├── controls.mdx │ │ ├── interaction.mdx │ │ ├── zoom-to-bounds.mdx │ │ ├── draggable-markers.mdx │ │ ├── geojson-animation.mdx │ │ ├── viewport-animation.mdx │ │ └── index.mdx │ ├── docs-sidebar.js │ └── examples-sidebar.js ├── static │ ├── favicon.ico │ └── images │ │ ├── hero.jpg │ │ ├── hero-sm.jpg │ │ ├── visgl-logo.png │ │ ├── examples │ │ ├── filter.jpg │ │ ├── layers.jpg │ │ ├── clusters.jpg │ │ ├── controls.jpg │ │ ├── geocoder.jpg │ │ ├── geojson.jpg │ │ ├── heatmap.jpg │ │ ├── terrain.jpg │ │ ├── interaction.jpg │ │ ├── custom-cursor.jpg │ │ ├── draw-polygon.jpg │ │ ├── side-by-side.jpg │ │ ├── zoom-to-bounds.jpg │ │ ├── draggable-markers.jpg │ │ ├── geojson-animation.jpg │ │ └── viewport-animation.jpg │ │ ├── visgl-logo-dark.png │ │ └── visgl-logo-light.png ├── babel.config.js ├── package.json └── ocular-docusaurus-plugin │ └── index.js ├── src ├── index.ts ├── utils │ ├── assert.ts │ ├── use-isomorphic-layout-effect.ts │ ├── apply-react-style.ts │ ├── deep-equal.ts │ ├── style-utils.ts │ └── set-globals.ts ├── types │ ├── public.ts │ └── style-spec-mapbox.ts └── components │ ├── navigation-control.ts │ ├── attribution-control.ts │ ├── fullscreen-control.tsx │ ├── scale-control.ts │ └── use-control.ts ├── test ├── size │ ├── map.js │ └── all.js ├── data │ ├── glyph │ │ └── UberMove │ │ │ └── 0-255 │ ├── tile │ │ └── v1 │ │ │ └── 12 │ │ │ └── 655 │ │ │ ├── 1582 │ │ │ ├── POI │ │ │ └── COMPOSITE │ │ │ └── 1583 │ │ │ ├── POI │ │ │ └── COMPOSITE │ └── sprite │ │ └── tools │ │ └── 14 │ │ └── sprites.png ├── .eslintrc ├── render │ └── golden-images │ │ ├── marker.png │ │ ├── popup.png │ │ ├── uber-map.png │ │ ├── basic-map.png │ │ ├── source-01.png │ │ ├── source-02.png │ │ ├── alt-empty-map.png │ │ ├── geolocate-control.png │ │ └── navigation-control.png ├── apps │ └── reuse-maps │ │ ├── vite.config.js │ │ ├── tsconfig.json │ │ ├── package.json │ │ ├── index.html │ │ └── src │ │ └── app.tsx ├── browser.js ├── node.js ├── src │ ├── utils │ │ ├── react-dom-mock.js │ │ ├── test-utils.jsx │ │ ├── mapbox-gl-mock │ │ │ ├── globals.js │ │ │ ├── util.js │ │ │ ├── index.js │ │ │ ├── task_queue.js │ │ │ ├── popup.js │ │ │ └── marker.js │ │ └── apply-react-style.spec.js │ ├── index.js │ └── components │ │ ├── controls.spec.jsx │ │ ├── use-map.spec.jsx │ │ ├── source.spec.jsx │ │ └── popup.spec.jsx └── test-utils.js ├── .eslintignore ├── examples ├── .eslintrc ├── clusters │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ ├── layers.ts │ │ └── app.tsx │ ├── README.md │ └── index.html ├── controls │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── pin.tsx │ │ └── control-panel.tsx │ ├── README.md │ └── index.html ├── filter │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ └── map-style.ts │ ├── README.md │ └── index.html ├── geocoder │ ├── vite.config.js │ ├── tsconfig.json │ ├── src │ │ ├── control-panel.tsx │ │ └── app.tsx │ ├── package.json │ ├── README.md │ └── index.html ├── geojson │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── map-style.ts │ │ ├── utils.ts │ │ └── control-panel.tsx │ ├── README.md │ └── index.html ├── heatmap │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── index.html │ └── src │ │ └── map-style.ts ├── layers │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── src │ │ └── app.tsx │ └── index.html ├── terrain │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ └── app.tsx │ ├── README.md │ └── index.html ├── custom-cursor │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── index.html │ └── src │ │ ├── app.tsx │ │ └── control-panel.tsx ├── custom-overlay │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── src │ │ ├── control-panel.tsx │ │ └── custom-overlay.tsx │ └── index.html ├── deckgl-overlay │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── index.html │ ├── README.md │ └── src │ │ └── app.tsx ├── draw-polygon │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ ├── draw-control.ts │ │ └── app.tsx │ ├── README.md │ └── index.html ├── interaction │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── index.html │ └── src │ │ ├── app.tsx │ │ └── control-panel.tsx ├── side-by-side │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── index.html │ └── src │ │ └── control-panel.tsx ├── zoom-to-bounds │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ ├── map-style.tsx │ │ └── app.tsx │ ├── README.md │ └── index.html ├── draggable-markers │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── pin.tsx │ │ ├── control-panel.tsx │ │ └── app.tsx │ ├── README.md │ └── index.html ├── geojson-animation │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── src │ │ ├── control-panel.tsx │ │ └── app.tsx │ ├── README.md │ └── index.html ├── get-started │ ├── basic │ │ ├── vite.config.js │ │ ├── index.html │ │ ├── package.json │ │ ├── README.md │ │ └── app.jsx │ ├── hook │ │ ├── vite.config.js │ │ ├── index.html │ │ ├── package.json │ │ ├── app.jsx │ │ ├── map.jsx │ │ ├── README.md │ │ └── controls.jsx │ ├── redux │ │ ├── vite.config.js │ │ ├── index.html │ │ ├── package.json │ │ ├── app.jsx │ │ ├── store.js │ │ ├── README.md │ │ ├── map.jsx │ │ └── controls.jsx │ ├── controlled │ │ ├── vite.config.js │ │ ├── index.html │ │ ├── package.json │ │ ├── README.md │ │ └── app.jsx │ ├── nextjs │ │ ├── pages │ │ │ ├── _app.js │ │ │ └── index.js │ │ ├── next.config.js │ │ ├── package.json │ │ └── .gitignore │ └── maplibre │ │ ├── index.html │ │ ├── package.json │ │ ├── README.md │ │ └── app.jsx ├── viewport-animation │ ├── vite.config.js │ ├── tsconfig.json │ ├── package.json │ ├── README.md │ ├── src │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── index.html └── vite.config.local.js ├── .prettierrc ├── babel.config.cjs ├── maplibre └── package.json ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.yml │ └── bug-report.yml └── workflows │ ├── test.yml │ ├── release.yml │ └── website.yml ├── index.html ├── .gitignore ├── tsconfig.build.json ├── tsconfig.json ├── .ocularrc.js ├── TESTING.md ├── scripts ├── update-release-branch.sh └── github-release.js └── .eslintrc.cjs /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ../CONTRIBUTING.md -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | ../dist 2 | ../examples 3 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | api-reference/web-mercator-viewport.md 2 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | public/ 2 | .cache/ 3 | .docusaurus/ 4 | build/ 5 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './exports-mapbox'; 2 | export {default as default} from './exports-mapbox'; 3 | -------------------------------------------------------------------------------- /test/size/map.js: -------------------------------------------------------------------------------- 1 | import {Map} from 'react-map-gl'; 2 | 3 | console.log(Map); // eslint-disable-line 4 | -------------------------------------------------------------------------------- /website/src/components/common.jsx: -------------------------------------------------------------------------------- 1 | export const isMobile = props => `@media screen and (max-width: 480px)`; 2 | -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/favicon.ico -------------------------------------------------------------------------------- /test/data/glyph/UberMove/0-255: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/glyph/UberMove/0-255 -------------------------------------------------------------------------------- /test/size/all.js: -------------------------------------------------------------------------------- 1 | import * as ReactMapGL from 'react-map-gl'; 2 | 3 | console.log(ReactMapGL); // eslint-disable-line 4 | -------------------------------------------------------------------------------- /website/static/images/hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/hero.jpg -------------------------------------------------------------------------------- /test/data/tile/v1/12/655/1582/POI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/tile/v1/12/655/1582/POI -------------------------------------------------------------------------------- /test/data/tile/v1/12/655/1583/POI: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/tile/v1/12/655/1583/POI -------------------------------------------------------------------------------- /website/src/examples/clusters.mdx: -------------------------------------------------------------------------------- 1 | # Clusters 2 | 3 | import App from 'website-examples/clusters/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/geocoder.mdx: -------------------------------------------------------------------------------- 1 | # Geocoder 2 | 3 | import App from 'website-examples/geocoder/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/geojson.mdx: -------------------------------------------------------------------------------- 1 | # GeoJSON 2 | 3 | import App from 'website-examples/geojson/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/heatmap.mdx: -------------------------------------------------------------------------------- 1 | # Heatmap 2 | 3 | import App from 'website-examples/heatmap/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/terrain.mdx: -------------------------------------------------------------------------------- 1 | # Terrain 2 | 3 | import App from 'website-examples/terrain/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/hero-sm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/hero-sm.jpg -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-unresolved": 0, 4 | "import/no-extraneous-dependencies": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/render/golden-images/marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/marker.png -------------------------------------------------------------------------------- /test/render/golden-images/popup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/popup.png -------------------------------------------------------------------------------- /website/src/examples/layers.mdx: -------------------------------------------------------------------------------- 1 | # Dynamic Styling 2 | 3 | import App from 'website-examples/layers/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/visgl-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/visgl-logo.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | test/src/utils/mapbox-gl-mock/*.js 4 | **/vite.config.js 5 | examples/vite.config.local.js 6 | -------------------------------------------------------------------------------- /examples/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-unresolved": 0, 4 | "import/no-extraneous-dependencies": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/data/sprite/tools/14/sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/sprite/tools/14/sprites.png -------------------------------------------------------------------------------- /test/render/golden-images/uber-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/uber-map.png -------------------------------------------------------------------------------- /website/src/examples/filter.mdx: -------------------------------------------------------------------------------- 1 | # Highlight By Filter 2 | 3 | import App from 'website-examples/filter/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | semi: true 3 | singleQuote: true 4 | trailingComma: none 5 | bracketSpacing: false 6 | arrowParens: avoid 7 | -------------------------------------------------------------------------------- /test/data/tile/v1/12/655/1582/COMPOSITE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/tile/v1/12/655/1582/COMPOSITE -------------------------------------------------------------------------------- /test/data/tile/v1/12/655/1583/COMPOSITE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/data/tile/v1/12/655/1583/COMPOSITE -------------------------------------------------------------------------------- /test/render/golden-images/basic-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/basic-map.png -------------------------------------------------------------------------------- /test/render/golden-images/source-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/source-01.png -------------------------------------------------------------------------------- /test/render/golden-images/source-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/source-02.png -------------------------------------------------------------------------------- /website/src/examples/custom-cursor.mdx: -------------------------------------------------------------------------------- 1 | # Custom Cursor 2 | 3 | import App from 'website-examples/custom-cursor/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/draw-polygon.mdx: -------------------------------------------------------------------------------- 1 | # Draw Polygon 2 | 3 | import App from 'website-examples/draw-polygon/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/side-by-side.mdx: -------------------------------------------------------------------------------- 1 | # Side by Side 2 | 3 | import App from 'website-examples/side-by-side/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/filter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/filter.jpg -------------------------------------------------------------------------------- /website/static/images/examples/layers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/layers.jpg -------------------------------------------------------------------------------- /website/static/images/visgl-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/visgl-logo-dark.png -------------------------------------------------------------------------------- /test/render/golden-images/alt-empty-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/alt-empty-map.png -------------------------------------------------------------------------------- /website/src/examples/controls.mdx: -------------------------------------------------------------------------------- 1 | # Markers, Popups and Controls 2 | 3 | import App from 'website-examples/controls/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/interaction.mdx: -------------------------------------------------------------------------------- 1 | # Limit Map Interaction 2 | 3 | import App from 'website-examples/interaction/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/zoom-to-bounds.mdx: -------------------------------------------------------------------------------- 1 | # Zoom to Bounds 2 | 3 | import App from 'website-examples/zoom-to-bounds/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/clusters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/clusters.jpg -------------------------------------------------------------------------------- /website/static/images/examples/controls.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/controls.jpg -------------------------------------------------------------------------------- /website/static/images/examples/geocoder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/geocoder.jpg -------------------------------------------------------------------------------- /website/static/images/examples/geojson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/geojson.jpg -------------------------------------------------------------------------------- /website/static/images/examples/heatmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/heatmap.jpg -------------------------------------------------------------------------------- /website/static/images/examples/terrain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/terrain.jpg -------------------------------------------------------------------------------- /website/static/images/visgl-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/visgl-logo-light.png -------------------------------------------------------------------------------- /website/src/components/index.js: -------------------------------------------------------------------------------- 1 | export {default as ExamplesIndex} from './example/examples-index'; 2 | 3 | export {default as Home} from './home'; 4 | -------------------------------------------------------------------------------- /website/src/examples/draggable-markers.mdx: -------------------------------------------------------------------------------- 1 | # Draggable Marker 2 | 3 | import App from 'website-examples/draggable-markers/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/interaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/interaction.jpg -------------------------------------------------------------------------------- /test/render/golden-images/geolocate-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/geolocate-control.png -------------------------------------------------------------------------------- /test/render/golden-images/navigation-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/test/render/golden-images/navigation-control.png -------------------------------------------------------------------------------- /website/src/examples/geojson-animation.mdx: -------------------------------------------------------------------------------- 1 | # GeoJSON Animation 2 | 3 | import App from 'website-examples/geojson-animation/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/viewport-animation.mdx: -------------------------------------------------------------------------------- 1 | # Camera Transition 2 | 3 | import App from 'website-examples/viewport-animation/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/static/images/examples/custom-cursor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/custom-cursor.jpg -------------------------------------------------------------------------------- /website/static/images/examples/draw-polygon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/draw-polygon.jpg -------------------------------------------------------------------------------- /website/static/images/examples/side-by-side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/side-by-side.jpg -------------------------------------------------------------------------------- /src/utils/assert.ts: -------------------------------------------------------------------------------- 1 | export default function assert(condition: any, message: string) { 2 | if (!condition) { 3 | throw new Error(message); 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /website/static/images/examples/zoom-to-bounds.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/zoom-to-bounds.jpg -------------------------------------------------------------------------------- /website/static/images/examples/draggable-markers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/draggable-markers.jpg -------------------------------------------------------------------------------- /website/static/images/examples/geojson-animation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/geojson-animation.jpg -------------------------------------------------------------------------------- /website/static/images/examples/viewport-animation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/donmccurdy/react-map-gl/master/website/static/images/examples/viewport-animation.jpg -------------------------------------------------------------------------------- /examples/clusters/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/controls/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/filter/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/geocoder/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/geojson/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/heatmap/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/layers/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/terrain/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /babel.config.cjs: -------------------------------------------------------------------------------- 1 | const {getBabelConfig} = require('ocular-dev-tools/configuration'); 2 | 3 | module.exports = getBabelConfig({ 4 | react: true, 5 | overrides: {} 6 | }); 7 | -------------------------------------------------------------------------------- /examples/custom-cursor/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/custom-overlay/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/draw-polygon/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/interaction/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/side-by-side/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /test/apps/reuse-maps/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/draggable-markers/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/geojson-animation/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started/basic/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started/hook/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started/redux/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/viewport-animation/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started/controlled/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | define: { 3 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 4 | } 5 | }; 6 | -------------------------------------------------------------------------------- /examples/get-started/nextjs/pages/_app.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export default function MyApp({Component, pageProps}) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /maplibre/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "internal": true, 3 | "main": "../dist/exports-maplibre.cjs", 4 | "module": "../dist/exports-maplibre.js", 5 | "types": "../dist/exports-maplibre.d.ts" 6 | } 7 | -------------------------------------------------------------------------------- /website/src/examples/index.mdx: -------------------------------------------------------------------------------- 1 | import {ExamplesIndex} from '../components'; 2 | 3 | `/images/examples/${item.docId || item.label.toLowerCase()}.jpg`} /> 4 | -------------------------------------------------------------------------------- /test/browser.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import test from 'tape'; 3 | 4 | test.onFinish(window.browserTestDriver_finish); 5 | test.onFailure(window.browserTestDriver_fail); 6 | 7 | import './render'; 8 | -------------------------------------------------------------------------------- /test/node.js: -------------------------------------------------------------------------------- 1 | import {JSDOM} from 'jsdom'; 2 | 3 | const dom = new JSDOM(`
`); 4 | /* global global */ 5 | global.document = dom.window.document; 6 | 7 | import './src'; 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: I have a question / I need help 4 | url: https://github.com/visgl/react-map-gl/discussions 5 | about: Ask generic questions or request help here 6 | -------------------------------------------------------------------------------- /test/src/utils/react-dom-mock.js: -------------------------------------------------------------------------------- 1 | // This file is used as a replacement of the react-dom module during node tests 2 | import * as React from 'react'; 3 | 4 | export function createPortal(element, container) { 5 | return
{element}
; 6 | } 7 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl unit tests 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/types/public.ts: -------------------------------------------------------------------------------- 1 | export type {ViewState, ControlPosition} from './common'; 2 | export type { 3 | IControl, 4 | MapInstance, 5 | MapLib, 6 | CustomLayerInterface, 7 | CustomSource, 8 | CustomSourceImplementation 9 | } from './lib'; 10 | -------------------------------------------------------------------------------- /examples/get-started/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/get-started/hook/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/get-started/redux/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/get-started/controlled/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/get-started/maplibre/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/apps/reuse-maps/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "module": "ES2020" 9 | } 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | dist-es6/ 3 | 4 | node_modules 5 | .nyc_output/ 6 | coverage 7 | npm-debug.log 8 | 9 | package-lock.json 10 | examples/**/yarn.lock 11 | test/**/yarn.lock 12 | */**/package-lock.json 13 | yarn-error.log 14 | .DS_Store 15 | .reify-cache 16 | 17 | .idea 18 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | plugins: [ 4 | 'version-inline', 5 | // Ensure consistently hashed component classNames between environments (a must for SSR) 6 | 'styled-components' 7 | ] 8 | }; 9 | -------------------------------------------------------------------------------- /examples/clusters/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/controls/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/filter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/geocoder/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/geojson/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/heatmap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/layers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/terrain/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/custom-cursor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/custom-overlay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/deckgl-overlay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/draw-polygon/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/interaction/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/side-by-side/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/zoom-to-bounds/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/draggable-markers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/geojson-animation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /examples/viewport-animation/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "allowSyntheticDefaultImports": true, 6 | "resolveJsonModule": true, 7 | "moduleResolution": "node", 8 | "module": "ES2020", 9 | "sourceMap": true 10 | } 11 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "jsx": "react", 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "module": "ES2020", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "outDir": "./dist" 11 | }, 12 | "include":[ 13 | "src/**/*" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/get-started/nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | 3 | module.exports = { 4 | reactStrictMode: true, 5 | 6 | webpack: config => { 7 | // Optional: Enables reading mapbox token from environment variable 8 | config.plugins.push(new webpack.EnvironmentPlugin({MapboxAccessToken: ''})); 9 | return config; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /examples/get-started/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "build": "vite build" 5 | }, 6 | "dependencies": { 7 | "react": "^18.0.0", 8 | "react-dom": "^18.0.0", 9 | "react-map-gl": "^7.1.0", 10 | "mapbox-gl": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.0.0", 14 | "vite": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/get-started/hook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "build": "vite build" 5 | }, 6 | "dependencies": { 7 | "react": "^18.0.0", 8 | "react-dom": "^18.0.0", 9 | "react-map-gl": "^7.1.0", 10 | "mapbox-gl": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.0.0", 14 | "vite": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/get-started/controlled/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "build": "vite build" 5 | }, 6 | "dependencies": { 7 | "react": "^18.0.0", 8 | "react-dom": "^18.0.0", 9 | "react-map-gl": "^7.1.0", 10 | "mapbox-gl": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.0.0", 14 | "vite": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/get-started/maplibre/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "build": "vite build" 5 | }, 6 | "dependencies": { 7 | "maplibre-gl": "^2.0.0", 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.0.0", 14 | "vite": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/use-isomorphic-layout-effect.ts: -------------------------------------------------------------------------------- 1 | // From https://github.com/streamich/react-use/blob/master/src/useIsomorphicLayoutEffect.ts 2 | // useLayoutEffect but does not trigger warning in server-side rendering 3 | import {useEffect, useLayoutEffect} from 'react'; 4 | 5 | const useIsomorphicLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect; 6 | 7 | export default useIsomorphicLayoutEffect; 8 | -------------------------------------------------------------------------------- /examples/get-started/redux/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "build": "vite build" 5 | }, 6 | "dependencies": { 7 | "react": "^18.0.0", 8 | "react-dom": "^18.0.0", 9 | "react-map-gl": "^7.1.0", 10 | "react-redux": "^7.0.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/apps/reuse-maps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../../examples/vite.config.local.js" 5 | }, 6 | "dependencies": { 7 | "react": "^17.0.0", 8 | "react-dom": "^17.0.0", 9 | "react-map-gl": "^7.0.0", 10 | "mapbox-gl": "^2.0.0" 11 | }, 12 | "devDependencies": { 13 | "typescript": "^4.0.0", 14 | "vite": "^4.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "jsx": "react", 6 | "allowJs": true, 7 | "allowSyntheticDefaultImports": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "moduleResolution": "node", 11 | "noEmit": true 12 | }, 13 | "include":[ 14 | "examples/**/*", 15 | "src/**/*", 16 | "test/**/*" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /examples/clusters/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/controls/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/filter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "mapbox-gl": "^2.0.0", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/heatmap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/terrain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "mapbox-gl": "^2.0.0", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/custom-cursor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/side-by-side/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "mapbox-gl": "^2.0.0", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/src/index.js: -------------------------------------------------------------------------------- 1 | import './components/map.spec'; 2 | import './components/controls.spec'; 3 | import './components/source.spec'; 4 | import './components/layer.spec'; 5 | import './components/marker.spec'; 6 | import './components/popup.spec'; 7 | import './components/use-map.spec'; 8 | 9 | import './utils/deep-equal.spec'; 10 | import './utils/transform.spec'; 11 | import './utils/style-utils.spec'; 12 | import './utils/apply-react-style.spec'; 13 | -------------------------------------------------------------------------------- /examples/draggable-markers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "mapbox-gl": "^2.0.0", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/geojson-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/viewport-animation/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "react": "^18.0.0", 9 | "react-dom": "^18.0.0", 10 | "react-map-gl": "^7.1.0", 11 | "mapbox-gl": "^2.0.0" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^4.0.0", 15 | "vite": "^4.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/layers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "immutable": "^4.0.0", 9 | "mapbox-gl": "^2.0.0", 10 | "react": "^18.0.0", 11 | "react-dom": "^18.0.0", 12 | "react-map-gl": "^7.1.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.0.0", 16 | "vite": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /website/src/docs-sidebar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | const sidebars = { 12 | docsSidebar: require('../../docs/table-of-contents.json') 13 | }; 14 | 15 | module.exports = sidebars; 16 | -------------------------------------------------------------------------------- /examples/custom-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "d3-shape": "^3.1.0", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0", 12 | "mapbox-gl": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.0.0", 16 | "vite": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/get-started/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "next dev", 4 | "build": "next build", 5 | "start": "next start", 6 | "lint": "next lint" 7 | }, 8 | "dependencies": { 9 | "next": "12.0.7", 10 | "react": "17.0.2", 11 | "react-dom": "17.0.2", 12 | "react-map-gl": "^7.1.0", 13 | "mapbox-gl": "^2.0.0" 14 | }, 15 | "devDependencies": { 16 | "eslint": "8.5.0", 17 | "eslint-config-next": "12.0.7" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@turf/bbox": "^6.5.0", 9 | "mapbox-gl": "^2.0.0", 10 | "react": "^18.0.0", 11 | "react-dom": "^18.0.0", 12 | "react-map-gl": "^7.1.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.0.0", 16 | "vite": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "deck.gl": "^8.8.0-beta.2", 9 | "react": "^18.0.0", 10 | "react-dom": "^18.0.0", 11 | "react-map-gl": "^7.1.0", 12 | "mapbox-gl": "^2.0.0" 13 | }, 14 | "devDependencies": { 15 | "typescript": "^4.0.0", 16 | "vite": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/get-started/maplibre/README.md: -------------------------------------------------------------------------------- 1 | # react-map-gl Example: Using with Maplibre 2 | 3 | This example shows a minimal app configuration to use react-map-gl with [Maplibre GL JS](https://maplibre.org/). 4 | 5 | ## Usage 6 | 7 | ```bash 8 | npm i 9 | npm run start 10 | ``` 11 | 12 | To build a production version: 13 | 14 | ```bash 15 | npm run build 16 | ``` 17 | 18 | ## Attribution 19 | 20 | The basemap in this example is provided by [CARTO free basemap service](https://carto.com/basemaps). -------------------------------------------------------------------------------- /test/src/utils/test-utils.jsx: -------------------------------------------------------------------------------- 1 | /* global setTimeout */ 2 | export function sleep(milliseconds) { 3 | return new Promise(resolve => setTimeout(resolve, milliseconds)); 4 | } 5 | 6 | export function waitForMapLoad(mapRef) { 7 | return new Promise(resolve => { 8 | const check = () => { 9 | if (mapRef.current && mapRef.current.getMap().isStyleLoaded()) { 10 | resolve(); 11 | } else { 12 | setTimeout(check, 50); 13 | } 14 | }; 15 | check(); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /examples/geojson/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "d3-array": "^3.1.1", 9 | "d3-scale": "^4.0.2", 10 | "mapbox-gl": "^2.0.0", 11 | "react": "^18.0.0", 12 | "react-dom": "^18.0.0", 13 | "react-map-gl": "^7.1.0" 14 | }, 15 | "devDependencies": { 16 | "typescript": "^4.0.0", 17 | "vite": "^4.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/get-started/hook/app.jsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import {MapProvider} from 'react-map-gl'; 5 | 6 | import Map from './map'; 7 | import Controls from './controls'; 8 | 9 | function Root() { 10 | return ( 11 | 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const root = createRoot(document.body.appendChild(document.createElement('div'))); 19 | root.render(); 20 | -------------------------------------------------------------------------------- /examples/geocoder/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel(props) { 4 | return ( 5 |
6 |

Geocoder

7 | 15 |
16 | ); 17 | } 18 | 19 | export default React.memo(ControlPanel); 20 | -------------------------------------------------------------------------------- /examples/geocoder/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@mapbox/mapbox-gl-geocoder": "^4.7.4", 9 | "@types/mapbox__mapbox-gl-geocoder": "^4.7.2", 10 | "react": "^18.0.0", 11 | "react-dom": "^18.0.0", 12 | "react-map-gl": "^7.1.0", 13 | "mapbox-gl": "^2.0.0" 14 | }, 15 | "devDependencies": { 16 | "typescript": "^4.0.0", 17 | "vite": "^4.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/get-started/redux/app.jsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import {Provider} from 'react-redux'; 5 | import store from './store'; 6 | 7 | import Map from './map'; 8 | import Controls from './controls'; 9 | 10 | function Root() { 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | const root = createRoot(document.body.appendChild(document.createElement('div'))); 20 | root.render(); 21 | -------------------------------------------------------------------------------- /examples/get-started/redux/store.js: -------------------------------------------------------------------------------- 1 | import {createStore} from 'redux'; 2 | 3 | function mapStateReducer(state, action) { 4 | switch (action.type) { 5 | case 'setViewState': 6 | return {...state, viewState: action.payload}; 7 | 8 | default: 9 | return state; 10 | } 11 | } 12 | 13 | const defaultMapState = { 14 | mapStyle: 'mapbox://styles/mapbox/streets-v11', 15 | viewState: { 16 | latitude: 37.8, 17 | longitude: -122.4, 18 | zoom: 14 19 | } 20 | }; 21 | 22 | export default createStore(mapStateReducer, defaultMapState); 23 | -------------------------------------------------------------------------------- /examples/draw-polygon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "start": "vite --open", 4 | "start-local": "vite --config ../vite.config.local.js", 5 | "build": "vite build" 6 | }, 7 | "dependencies": { 8 | "@mapbox/mapbox-gl-draw": "^1.3.0", 9 | "@types/mapbox__mapbox-gl-draw": "^1.2.3", 10 | "@turf/area": "^6.5.0", 11 | "react": "^18.0.0", 12 | "react-dom": "^18.0.0", 13 | "react-map-gl": "^7.1.0", 14 | "mapbox-gl": "^2.0.0" 15 | }, 16 | "devDependencies": { 17 | "typescript": "^4.0.0", 18 | "vite": "^4.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/get-started/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | -------------------------------------------------------------------------------- /examples/terrain/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel(props) { 4 | return ( 5 |
6 |

3D Terrain

7 |

Add 3D terrain and sky to a map.

8 | 9 | 17 |
18 | ); 19 | } 20 | 21 | export default React.memo(ControlPanel); 22 | -------------------------------------------------------------------------------- /examples/get-started/hook/map.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Map from 'react-map-gl'; 3 | 4 | import 'mapbox-gl/dist/mapbox-gl.css'; 5 | 6 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 7 | 8 | export default function MapView() { 9 | return ( 10 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/geojson-animation/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Animated GeoJSON

7 |

Render animation by updating GeoJSON data source.

8 | 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(ControlPanel); 21 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Zoom to Bounding Box

7 |

Click on a San Fransisco Neighborhood to zoom in.

8 | 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(ControlPanel); 21 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/globals.js: -------------------------------------------------------------------------------- 1 | export function supported() { 2 | return true; 3 | } 4 | 5 | let rtlTextPlugin = ''; 6 | let baseApiUrl = 'https://api.mapbox.com'; 7 | 8 | export function getRTLTextPluginStatus() { 9 | return rtlTextPlugin ? 'deferred' : 'unavailable'; 10 | } 11 | 12 | export function setRTLTextPlugin(url) { 13 | rtlTextPlugin = url; 14 | } 15 | 16 | export default { 17 | supported, 18 | getRTLTextPluginStatus, 19 | setRTLTextPlugin, 20 | get baseApiUrl() { 21 | return baseApiUrl; 22 | }, 23 | set baseApiUrl(value) { 24 | baseApiUrl = value; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /examples/clusters/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Create and Style Clusters

7 |

Use Mapbox GL JS' built-in functions to visualize points as clusters.

8 | 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(ControlPanel); 21 | -------------------------------------------------------------------------------- /examples/filter/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Highlight Features Containing Similar Data

7 |

Hover over counties to highlight counties that share the same name.

8 | 16 |
17 | ); 18 | } 19 | 20 | export default React.memo(ControlPanel); 21 | -------------------------------------------------------------------------------- /.ocularrc.js: -------------------------------------------------------------------------------- 1 | import {resolve} from 'path'; 2 | 3 | export default { 4 | lint: { 5 | paths: ['src', 'test', 'examples'] 6 | }, 7 | 8 | typescript: { 9 | project: 'tsconfig.build.json' 10 | }, 11 | 12 | aliases: { 13 | 'react-map-gl/test': resolve('./test'), 14 | 'react-map-gl': resolve('./src') 15 | }, 16 | nodeAliases: { 17 | 'react-dom': resolve('./test/src/utils/react-dom-mock.js') 18 | }, 19 | 20 | browserTest: { 21 | server: {wait: 5000} 22 | }, 23 | 24 | entry: { 25 | test: 'test/node.js', 26 | 'test-browser': 'test/browser.js', 27 | size: ['test/size/all.js', 'test/size/map.js'] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/util.js: -------------------------------------------------------------------------------- 1 | // Generated with 2 | // flow-remove-types ./node_modules/mapbox-gl/src/util/util.js 3 | 4 | export function clamp(n, min, max) { 5 | return Math.min(max, Math.max(min, n)); 6 | } 7 | 8 | export function wrap(n, min, max) { 9 | const d = max - min; 10 | const w = ((((n - min) % d) + d) % d) + min; 11 | return w === min ? max : w; 12 | } 13 | 14 | export function extend(dest, ...sources) { 15 | for (const src of sources) { 16 | for (const k in src) { 17 | dest[k] = src[k]; 18 | } 19 | } 20 | return dest; 21 | } 22 | 23 | export function number(a, b, t) { 24 | return a * (1 - t) + b * t; 25 | } 26 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Unit, Lint 4 | 5 | ``` 6 | npm run test 7 | ``` 8 | 9 | ## Browser 10 | 11 | ``` 12 | npm run test-browser 13 | ``` 14 | 15 | **You'll need to specify a valid Mapbox Access Token in the URL** for the tests to pass. 16 | 17 | ``` 18 | http://localhost:8080/?access_token=MAPBOX_ACCESS_TOKEN 19 | ``` 20 | 21 | # Bumping Mapbox Version 22 | 23 | Always pin Mapbox to a specific release. 24 | 25 | The React controls (`NavigationControl`, `Popup` and `Marker`) are dependent on 26 | the Mapbox stylesheet, and may be broken by Mapbox updates. 27 | Always run `examples/controls` after bumping Mapbox version to make sure they 28 | still work. 29 | -------------------------------------------------------------------------------- /examples/controls/src/pin.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3 4 | c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9 5 | C20.1,15.8,20.2,15.8,20.2,15.7z`; 6 | 7 | const pinStyle = { 8 | cursor: 'pointer', 9 | fill: '#d00', 10 | stroke: 'none' 11 | }; 12 | 13 | function Pin({size = 20}) { 14 | return ( 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | export default React.memo(Pin); 22 | -------------------------------------------------------------------------------- /examples/filter/src/map-style.ts: -------------------------------------------------------------------------------- 1 | import type {FillLayer} from 'react-map-gl'; 2 | 3 | export const countiesLayer: FillLayer = { 4 | id: 'counties', 5 | source: '', 6 | type: 'fill', 7 | 'source-layer': 'original', 8 | paint: { 9 | 'fill-outline-color': 'rgba(0,0,0,0.1)', 10 | 'fill-color': 'rgba(0,0,0,0.1)' 11 | } 12 | }; 13 | // Highlighted county polygons 14 | export const highlightLayer: FillLayer = { 15 | id: 'counties-highlighted', 16 | type: 'fill', 17 | source: 'counties', 18 | 'source-layer': 'original', 19 | paint: { 20 | 'fill-outline-color': '#484896', 21 | 'fill-color': '#6e599f', 22 | 'fill-opacity': 0.75 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /examples/draggable-markers/src/pin.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const ICON = `M20.2,15.7L20.2,15.7c1.1-1.6,1.8-3.6,1.8-5.7c0-5.6-4.5-10-10-10S2,4.5,2,10c0,2,0.6,3.9,1.6,5.4c0,0.1,0.1,0.2,0.2,0.3 4 | c0,0,0.1,0.1,0.1,0.2c0.2,0.3,0.4,0.6,0.7,0.9c2.6,3.1,7.4,7.6,7.4,7.6s4.8-4.5,7.4-7.5c0.2-0.3,0.5-0.6,0.7-0.9 5 | C20.1,15.8,20.2,15.8,20.2,15.7z`; 6 | 7 | const pinStyle = { 8 | fill: '#d00', 9 | stroke: 'none' 10 | }; 11 | 12 | function Pin(props) { 13 | const {size = 20} = props; 14 | 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default React.memo(Pin); 23 | -------------------------------------------------------------------------------- /examples/geojson/src/map-style.ts: -------------------------------------------------------------------------------- 1 | import type {LayerProps} from 'react-map-gl'; 2 | 3 | // For more information on data-driven styles, see https://www.mapbox.com/help/gl-dds-ref/ 4 | export const dataLayer: LayerProps = { 5 | id: 'data', 6 | type: 'fill', 7 | paint: { 8 | 'fill-color': { 9 | type: 'interval', 10 | property: 'percentile', 11 | stops: [ 12 | [0, '#3288bd'], 13 | [1, '#66c2a5'], 14 | [2, '#abdda4'], 15 | [3, '#e6f598'], 16 | [4, '#ffffbf'], 17 | [5, '#fee08b'], 18 | [6, '#fdae61'], 19 | [7, '#f46d43'], 20 | [8, '#d53e4f'] 21 | ] 22 | }, 23 | 'fill-opacity': 0.8 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /examples/get-started/basic/README.md: -------------------------------------------------------------------------------- 1 | # react-map-gl Example: Using Map as a stateful component 2 | 3 | This example shows a minimal app configuration to use react-map-gl's Map component with automatic view state updates. 4 | 5 | ## Usage 6 | 7 | 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 `app.js`, or set a `MapboxAccessToken` environment variable in the command line. 8 | 9 | Alternative to acquiring a Mapbox token, visit the [maplibre-gl example](../maplibre). 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | To build a production version: 17 | 18 | ```bash 19 | npm run build 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 18 | 19 | 20 |
21 | 22 | 26 | 27 | -------------------------------------------------------------------------------- /src/utils/apply-react-style.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | // This is a simplified version of 3 | // https://github.com/facebook/react/blob/4131af3e4bf52f3a003537ec95a1655147c81270/src/renderers/dom/shared/CSSPropertyOperations.js#L62 4 | const unitlessNumber = /box|flex|grid|column|lineHeight|fontWeight|opacity|order|tabSize|zIndex/; 5 | 6 | export function applyReactStyle(element: HTMLElement, styles: React.CSSProperties) { 7 | if (!element || !styles) { 8 | return; 9 | } 10 | const style = element.style; 11 | 12 | for (const key in styles) { 13 | const value = styles[key]; 14 | if (Number.isFinite(value) && !unitlessNumber.test(key)) { 15 | style[key] = `${value}px`; 16 | } else { 17 | style[key] = value; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/get-started/maplibre/app.jsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map, {Marker} from 'react-map-gl/maplibre'; 5 | 6 | import 'maplibre-gl/dist/maplibre-gl.css'; 7 | 8 | function Root() { 9 | return ( 10 | 19 | 20 | 21 | ); 22 | } 23 | 24 | const root = createRoot(document.body.appendChild(document.createElement('div'))); 25 | root.render(); 26 | -------------------------------------------------------------------------------- /docs/api-reference/map-provider.md: -------------------------------------------------------------------------------- 1 | # MapProvider 2 | 3 | A [Context.Provider](https://reactjs.org/docs/context.html#contextprovider) that facilitates map operations outside of the component that directly renders a [Map](./map.md). 4 | 5 | The component should wrap all nodes in which you may want to access the maps: 6 | 7 | ```tsx 8 | import {MapProvider} from 'react-map-gl'; 9 | 10 | function Root() { 11 | return ( 12 | 13 | { 14 | // Application tree, somewhere one or more component(s) are rendered 15 | } 16 | 17 | ); 18 | } 19 | ``` 20 | 21 | See [useMap](./use-map.md) for more information. 22 | 23 | 24 | ## Source 25 | 26 | [use-map.tsx](https://github.com/visgl/react-map-gl/tree/7.0-release/src/components/use-map.tsx) 27 | -------------------------------------------------------------------------------- /examples/terrain/README.md: -------------------------------------------------------------------------------- 1 | # Example: 3D Terrain 2 | 3 | Demonstrates how to add 3D terrain with react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/interaction/README.md: -------------------------------------------------------------------------------- 1 | # Example: Interaction 2 | 3 | This example showcases how to toggle/limit user interaction. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/side-by-side/README.md: -------------------------------------------------------------------------------- 1 | # Example: Side By Side 2 | 3 | Demonstrates how to synchronize two maps with react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/controls/README.md: -------------------------------------------------------------------------------- 1 | # Example: Controls 2 | 3 | Demonstrates how various control components can be used with react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/geojson/README.md: -------------------------------------------------------------------------------- 1 | # Example: GeoJSON 2 | 3 | This example showcases how to dynamically add and update custom data sources. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/README.md: -------------------------------------------------------------------------------- 1 | # Example: Zoom To Bounds 2 | 3 | Demonstrates how to zoom to a bounding box with react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/layers/README.md: -------------------------------------------------------------------------------- 1 | # Example: Layers 2 | 3 | This example showcases how to dynamically change layer styles and show/hide layers. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/draggable-markers/README.md: -------------------------------------------------------------------------------- 1 | # Example: Draggable Marker 2 | 3 | Demonstrates how Marker component can be dragged with react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/get-started/redux/README.md: -------------------------------------------------------------------------------- 1 | # react-map-gl Example: Using Map with a State Management System 2 | 3 | This example shows how to use react-map-gl's Map component with react-redux. 4 | 5 | ## Usage 6 | 7 | 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 `map.js`, or set a `MapboxAccessToken` environment variable in the command line. 8 | 9 | Alternative to acquiring a Mapbox token, you can install `maplibre-gl` and change all `import from 'react-map-gl'` to `import from 'react-map-gl/maplibre'`. You also need to supply a third-party or self-hosted `mapStyle` URL. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | To build a production version: 17 | 18 | ```bash 19 | npm run build 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/geojson/src/utils.ts: -------------------------------------------------------------------------------- 1 | import {range} from 'd3-array'; 2 | import {scaleQuantile} from 'd3-scale'; 3 | 4 | import type GeoJSON from 'geojson'; 5 | 6 | export function updatePercentiles( 7 | featureCollection: GeoJSON.FeatureCollection, 8 | accessor: (f: GeoJSON.Feature) => number 9 | ): GeoJSON.FeatureCollection { 10 | const {features} = featureCollection; 11 | const scale = scaleQuantile().domain(features.map(accessor)).range(range(9)); 12 | return { 13 | type: 'FeatureCollection', 14 | features: features.map(f => { 15 | const value = accessor(f); 16 | const properties = { 17 | ...f.properties, 18 | value, 19 | percentile: scale(value) 20 | }; 21 | return {...f, properties}; 22 | }) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /examples/get-started/hook/README.md: -------------------------------------------------------------------------------- 1 | # react-map-gl Example: Using Map with a State Management System 2 | 3 | This example shows how to use react-map-gl's Map component with the `useMap` hook. 4 | 5 | ## Usage 6 | 7 | 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 `map.js`, or set a `MapboxAccessToken` environment variable in the command line. 8 | 9 | Alternative to acquiring a Mapbox token, you can install `maplibre-gl` and change all `import from 'react-map-gl'` to `import from 'react-map-gl/maplibre'`. You also need to supply a third-party or self-hosted `mapStyle` URL. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | To build a production version: 17 | 18 | ```bash 19 | npm run build 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/viewport-animation/README.md: -------------------------------------------------------------------------------- 1 | # Example: Viewport Animation 2 | 3 | This example showcases how to transition smoothly between one viewport to another. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/custom-cursor/README.md: -------------------------------------------------------------------------------- 1 | # Example: Custom Cursor 2 | 3 | This example showcases how to dynamically change the cursor over the map based on interactivity. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | # On every pull request, but only on push to master 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | 10 | jobs: 11 | test-node: 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | VITE_MAPBOX_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN_CI }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2.1.1 19 | 20 | - name: Use Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: '16.x' 24 | 25 | - name: Install dependencies 26 | run: | 27 | yarn bootstrap 28 | 29 | - name: Run tests 30 | run: | 31 | yarn test ci 32 | 33 | - name: Coveralls 34 | uses: coverallsapp/github-action@master 35 | with: 36 | github-token: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /examples/get-started/basic/app.jsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map, {Marker} from 'react-map-gl'; 5 | 6 | import 'mapbox-gl/dist/mapbox-gl.css'; 7 | 8 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 9 | 10 | function Root() { 11 | return ( 12 | 22 | 23 | 24 | ); 25 | } 26 | 27 | const root = createRoot(document.body.appendChild(document.createElement('div'))); 28 | root.render(); 29 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/README.md: -------------------------------------------------------------------------------- 1 | # Example: DeckGL Overlay 2 | 3 | This example demonstrates using [deck.gl](https://deck.gl) to render a data overlay on top of react-map-gl. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/geocoder/README.md: -------------------------------------------------------------------------------- 1 | # Example: Geocoder 2 | 3 | This app reproduces Mapbox's [Add a geocoder](https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-geocoder/) example. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/get-started/nextjs/pages/index.js: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Head from 'next/head'; 3 | import Map, {Marker} from 'react-map-gl'; 4 | 5 | import 'mapbox-gl/dist/mapbox-gl.css'; 6 | 7 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 8 | 9 | export default function Home() { 10 | return ( 11 |
12 | 13 | react-map-gl example 14 | 15 | 16 | 26 | 27 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /examples/custom-overlay/README.md: -------------------------------------------------------------------------------- 1 | # Example: SVG overlay 2 | 3 | This app shows how to implement a custom control that draws arbitrary React content that responds to camera updates. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /examples/get-started/controlled/README.md: -------------------------------------------------------------------------------- 1 | # react-map-gl Example: Using Map as a controlled component 2 | 3 | This example shows a minimal app configuration to use react-map-gl's Map component with external view state management. 4 | 5 | ## Usage 6 | 7 | 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 `app.js`, or set a `MapboxAccessToken` environment variable in the command line. 8 | 9 | Alternative to acquiring a Mapbox token, you can install `maplibre-gl` and change all `import from 'react-map-gl'` to `import from 'react-map-gl/maplibre'`. You also need to supply a third-party or self-hosted `mapStyle` URL. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | To build a production version: 17 | 18 | ```bash 19 | npm run build 20 | ``` 21 | -------------------------------------------------------------------------------- /examples/draw-polygon/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import area from '@turf/area'; 3 | 4 | function ControlPanel(props) { 5 | let polygonArea = 0; 6 | for (const polygon of props.polygons) { 7 | polygonArea += area(polygon); 8 | } 9 | 10 | return ( 11 |
12 |

Draw Polygon

13 | {polygonArea > 0 && ( 14 |

15 | {Math.round(polygonArea * 100) / 100}
16 | square meters 17 |

18 | )} 19 | 27 |
28 | ); 29 | } 30 | 31 | export default React.memo(ControlPanel); 32 | -------------------------------------------------------------------------------- /examples/get-started/redux/map.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Map from 'react-map-gl'; 3 | 4 | import {useCallback} from 'react'; 5 | import {useSelector, useDispatch} from 'react-redux'; 6 | 7 | import 'mapbox-gl/dist/mapbox-gl.css'; 8 | 9 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 10 | 11 | export default function MapView() { 12 | const mapStyle = useSelector(s => s.mapStyle); 13 | const viewState = useSelector(s => s.viewState); 14 | const dispatch = useDispatch(); 15 | 16 | const onMove = useCallback(evt => { 17 | dispatch({type: 'setViewState', payload: evt.viewState}); 18 | }, []); 19 | 20 | return ( 21 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /examples/heatmap/README.md: -------------------------------------------------------------------------------- 1 | # Example: Heatmap layer 2 | 3 | This app reproduces Mapbox's [Create a heatmap layer](https://docs.mapbox.com/mapbox-gl-js/example/heatmap-layer/) example. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/index.js: -------------------------------------------------------------------------------- 1 | // Adapted from https://github.com/mapbox/mapbox-gl-js-mock/ 2 | // BSD 3-Clause License 3 | // Copyright (c) 2017, Mapbox 4 | 5 | class FakeControl { 6 | constructor(opts) { 7 | this.options = opts; 8 | } 9 | addTo() {} 10 | onAdd() {} 11 | onRemove() {} 12 | on() {} 13 | } 14 | 15 | import Map from './map'; 16 | import LngLat from './lng_lat'; 17 | import LngLatBounds from './lng_lat_bounds'; 18 | import globals from './globals'; 19 | import Marker from './marker'; 20 | import Popup from './popup'; 21 | 22 | export default { 23 | ...globals, 24 | Map, 25 | LngLat, 26 | LngLatBounds, 27 | Marker, 28 | Popup, 29 | NavigationControl: FakeControl, 30 | ScaleControl: FakeControl, 31 | AttributionControl: FakeControl, 32 | GeolocateControl: FakeControl, 33 | FullscreenControl: FakeControl 34 | }; 35 | -------------------------------------------------------------------------------- /test/apps/reuse-maps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 24 | 25 | 26 |
27 | 28 | 32 | 33 | -------------------------------------------------------------------------------- /examples/draw-polygon/README.md: -------------------------------------------------------------------------------- 1 | # Example: Draw Polygon 2 | 3 | This app reproduces Mapbox's [Draw a polygon and calculate its area](https://docs.mapbox.com/mapbox-gl-js/example/mapbox-gl-draw/) example. 4 | 5 | ## Usage 6 | 7 | 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. 8 | 9 | ```bash 10 | npm i 11 | npm run start 12 | ``` 13 | 14 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 15 | - Run `npm install maplibre-gl` 16 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 17 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 18 | -------------------------------------------------------------------------------- /website/src/components/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 3 | import {Banner, BannerContainer, HeroExampleContainer, ProjectName, GetStartedLink} from './styled'; 4 | 5 | export default function renderPage({HeroExample, children}) { 6 | const {siteConfig} = useDocusaurusContext(); 7 | 8 | // Note: The Layout "wrapper" component adds header and footer etc 9 | return ( 10 | <> 11 | 12 | {HeroExample && } 13 | 14 | {siteConfig.title} 15 |

{siteConfig.tagline}

16 | GET STARTED 17 |
18 |
19 | {children} 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/layers/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map from 'react-map-gl'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 8 | 9 | export default function App() { 10 | const [mapStyle, setMapStyle] = useState(null); 11 | 12 | return ( 13 | <> 14 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | export function renderToDom(container) { 31 | createRoot(container).render(); 32 | } 33 | -------------------------------------------------------------------------------- /test/src/utils/apply-react-style.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'tape-promise/tape'; 2 | import {applyReactStyle} from 'react-map-gl/utils/apply-react-style'; 3 | 4 | test('applyReactStyle', t => { 5 | /* global document */ 6 | const div = document.createElement('div'); 7 | 8 | t.doesNotThrow(() => applyReactStyle(null, {}), 'null element'); 9 | 10 | t.doesNotThrow(() => applyReactStyle(div, null), 'null style'); 11 | 12 | applyReactStyle(div, {marginLeft: 4, height: 24, lineHeight: 2, zIndex: 1, flexGrow: 0.5}); 13 | 14 | t.is(div.style.marginLeft, '4px', 'appended px to numeric value'); 15 | t.is(div.style.height, '24px', 'appended px to numeric value'); 16 | t.is(div.style.lineHeight, '2', 'unitless numeric property'); 17 | t.is(div.style.zIndex, '1', 'unitless numeric property'); 18 | t.is(div.style.flexGrow, '0.5', 'unitless numeric property'); 19 | 20 | t.end(); 21 | }); 22 | -------------------------------------------------------------------------------- /website/src/components/example/doc-item-component.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import useBaseUrl from '@docusaurus/useBaseUrl'; 4 | 5 | const DemoContainer = styled.div` 6 | position: absolute; 7 | overflow: hidden !important; 8 | left: 0; 9 | right: 0; 10 | top: 0; 11 | bottom: 0; 12 | 13 | > h1 { 14 | display: none; 15 | } 16 | `; 17 | 18 | /** Passed to @docusaurus/plugin-content-docs to render the mdx content */ 19 | export default function DocItem({content, route}) { 20 | const MDXComponent = content; 21 | const indexPath = useBaseUrl('/examples'); 22 | 23 | if (route.path === indexPath) { 24 | return ( 25 |
26 | 27 |
28 | ); 29 | } 30 | 31 | return ( 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /examples/controls/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Marker, Popup, NavigationControl and FullscreenControl

7 |

8 | Map showing top 20 most populated cities of the United States. Click on a marker to learn 9 | more. 10 |

11 |

12 | Data source:{' '} 13 | 14 | Wikipedia 15 | 16 |

17 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/clusters/README.md: -------------------------------------------------------------------------------- 1 | # Example: Supercluster 2 | 3 | This app reproduces Mapbox's [Create and style clusters](https://docs.mapbox.com/mapbox-gl-js/example/cluster/) example. 4 | 5 | This example showcases how to visualize points as clusters. 6 | 7 | ## Usage 8 | 9 | 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. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 17 | - Run `npm install maplibre-gl` 18 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 19 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 20 | -------------------------------------------------------------------------------- /examples/geocoder/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import Map from 'react-map-gl'; 4 | 5 | import GeocoderControl from './geocoder-control'; 6 | import ControlPanel from './control-panel'; 7 | 8 | // eslint-disable-next-line 9 | const TOKEN = process.env.MapboxAccessToken; // Set your mapbox token here 10 | 11 | export default function App() { 12 | return ( 13 | <> 14 | 23 | 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | export function renderToDom(container) { 31 | createRoot(container).render(); 32 | } 33 | -------------------------------------------------------------------------------- /examples/get-started/controlled/app.jsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map, {Marker} from 'react-map-gl'; 5 | 6 | import 'mapbox-gl/dist/mapbox-gl.css'; 7 | 8 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 9 | 10 | function Root() { 11 | const [viewState, setViewState] = React.useState({ 12 | latitude: 37.8, 13 | longitude: -122.4, 14 | zoom: 14 15 | }); 16 | 17 | return ( 18 | setViewState(evt.viewState)} 21 | style={{width: 800, height: 600}} 22 | mapStyle="mapbox://styles/mapbox/streets-v9" 23 | mapboxAccessToken={MAPBOX_TOKEN} 24 | > 25 | 26 | 27 | ); 28 | } 29 | 30 | const root = createRoot(document.body.appendChild(document.createElement('div'))); 31 | root.render(); 32 | -------------------------------------------------------------------------------- /examples/filter/README.md: -------------------------------------------------------------------------------- 1 | # Example: Highlight By Filter 2 | 3 | This app reproduces Mapbox's [Highlight features containing similar data](https://www.mapbox.com/mapbox-gl-js/example/query-similar-features/) example. 4 | 5 | This example showcases how to dynamically add/remove filters from layers. 6 | 7 | ## Usage 8 | 9 | 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. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 17 | - Run `npm install maplibre-gl` 18 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 19 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 20 | -------------------------------------------------------------------------------- /examples/geojson-animation/README.md: -------------------------------------------------------------------------------- 1 | # Example: Animated GeoJSON 2 | 3 | This app reproduces Mapbox's [Animate point along line](https://www.mapbox.com/mapbox-gl-js/example/animate-point-along-line/) example. 4 | 5 | This example showcases how to dynamically add and update custom data sources. 6 | 7 | ## Usage 8 | 9 | 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. 10 | 11 | ```bash 12 | npm i 13 | npm run start 14 | ``` 15 | 16 | Alternative to acquiring a Mapbox token, you can use `maplibre-gl` instead. Follow these steps: 17 | - Run `npm install maplibre-gl` 18 | - In the source, change all `import ... from 'react-map-gl'` to `import ... from 'react-map-gl/maplibre'` 19 | - Change the `mapStyle` prop of `` to `"https://demotiles.maplibre.org/style.json"` or a self-hosted URL 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Request for a new feature or enhancement 3 | title: "[Feat]" 4 | labels: feature 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Tell us what you are using react-map-gl for and how we can make it better. 10 | This project is maintained by volunteers and sponsoring companies. While we cannot promise a timeline for any specific feature, we try to prioritize those that will benefit the most users. 11 | - type: textarea 12 | attributes: 13 | label: Target Use Case 14 | description: How would this benefit you and other developers? 15 | validations: 16 | required: true 17 | - type: textarea 18 | attributes: 19 | label: Proposal 20 | description: How would this feature work? If it's a new API, use code samples to show how it will be used. If it's visual, link to an image that illustrate the desired effect. 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | release-notes: 10 | runs-on: ubuntu-latest 11 | 12 | if: github.repository_owner == 'visgl' 13 | 14 | env: 15 | ADMIN_TOKEN: ${{ secrets.ADMIN_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2.1.1 19 | 20 | - name: Get git tags (https://github.com/actions/checkout/issues/206) 21 | run: git fetch --tags -f 22 | 23 | - name: Use Node.js 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: '16.x' 27 | 28 | - name: Publish release 29 | run: | 30 | body=$(node scripts/github-release.js) && 31 | curl \ 32 | -X POST \ 33 | -H "Accept: application/vnd.github.v3+json" \ 34 | https://api.github.com/repos/visgl/react-map-gl/releases \ 35 | -d "${body}" \ 36 | -H "Authorization: token ${ADMIN_TOKEN}" 37 | -------------------------------------------------------------------------------- /examples/vite.config.local.js: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite'; 2 | import {join} from 'path'; 3 | 4 | const rootDir = join(__dirname, '..'); 5 | 6 | /** https://vitejs.dev/config/ */ 7 | export default defineConfig(async () => { 8 | return { 9 | resolve: { 10 | alias: { 11 | // Use root dependencies 12 | 'react-map-gl': join(rootDir, './src/index.ts'), 13 | 'mapbox-gl': join(rootDir, './node_modules/mapbox-gl/dist/mapbox-gl-dev.js'), 14 | 'maplibre-gl': join(rootDir, './node_modules/maplibre-gl/dist/maplibre-gl-dev.js'), 15 | react: join(rootDir, './node_modules/react'), 16 | 'react-dom': join(rootDir, './node_modules/react-dom') 17 | } 18 | }, 19 | define: { 20 | 'process.env.MapboxAccessToken': JSON.stringify(process.env.MapboxAccessToken) 21 | }, 22 | server: { 23 | open: true, 24 | port: 8080 25 | }, 26 | optimizeDeps: { 27 | esbuildOptions: {target: 'es2020'} 28 | } 29 | }; 30 | }); 31 | -------------------------------------------------------------------------------- /test/test-utils.js: -------------------------------------------------------------------------------- 1 | const EPSILON = 1e-9; 2 | 3 | // Discard precision errors for comparison 4 | export function toLowPrecision(input, precision = 11) { 5 | if (Number.isFinite(input)) { 6 | return Number(input.toPrecision(precision)); 7 | } else if (Array.isArray(input)) { 8 | return input.map(item => toLowPrecision(item, precision)); 9 | } else if (input) { 10 | const output = {}; 11 | for (const key in input) { 12 | output[key] = toLowPrecision(input[key], precision); 13 | } 14 | return output; 15 | } 16 | return input; 17 | } 18 | 19 | // Compare two [lng, lat] locations, account for longitude wrapping 20 | export function isSameLocation(lngLat1, lngLat2) { 21 | const lng1 = toLowPrecision(lngLat1[0]); 22 | const lat1 = toLowPrecision(lngLat1[1]); 23 | const lng2 = toLowPrecision(lngLat2[0]); 24 | const lat2 = toLowPrecision(lngLat2[1]); 25 | return (lng1 - lng2) % 360 === 0 && lat1 === lat2; 26 | } 27 | 28 | export function equals(a, b, epsilon = EPSILON) { 29 | return Math.abs(a - b) < epsilon; 30 | } 31 | -------------------------------------------------------------------------------- /src/components/navigation-control.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useEffect, memo} from 'react'; 3 | import {applyReactStyle} from '../utils/apply-react-style'; 4 | import useControl from './use-control'; 5 | 6 | import type {ControlPosition, NavigationControlInstance} from '../types'; 7 | 8 | export type NavigationControlProps = OptionsT & { 9 | /** Placement of the control relative to the map. */ 10 | position?: ControlPosition; 11 | /** CSS style override, applied to the control's container */ 12 | style?: React.CSSProperties; 13 | }; 14 | 15 | function NavigationControl( 16 | props: NavigationControlProps 17 | ): null { 18 | const ctrl = useControl(({mapLib}) => new mapLib.NavigationControl(props) as ControlT, { 19 | position: props.position 20 | }); 21 | 22 | useEffect(() => { 23 | applyReactStyle(ctrl._container, props.style); 24 | }, [props.style]); 25 | 26 | return null; 27 | } 28 | 29 | export default memo(NavigationControl); 30 | -------------------------------------------------------------------------------- /website/src/examples-sidebar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | const sidebars = { 12 | examplesSidebar: [ 13 | { 14 | type: 'doc', 15 | label: 'Overview', 16 | id: 'index' 17 | }, 18 | { 19 | type: 'category', 20 | label: 'Examples', 21 | items: [ 22 | 'layers', 23 | 'controls', 24 | 'custom-cursor', 25 | 'draggable-markers', 26 | 'geojson', 27 | 'geojson-animation', 28 | 'clusters', 29 | 'interaction', 30 | 'viewport-animation', 31 | 'filter', 32 | 'zoom-to-bounds', 33 | 'heatmap', 34 | 'draw-polygon', 35 | 'terrain', 36 | 'geocoder', 37 | 'side-by-side' 38 | ] 39 | } 40 | ] 41 | }; 42 | 43 | module.exports = sidebars; 44 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/src/map-style.tsx: -------------------------------------------------------------------------------- 1 | import type {GeoJSONSourceRaw, FillLayer, LineLayer} from 'react-map-gl'; 2 | 3 | import MAP_STYLE from '../../map-style-basic-v8.json'; 4 | 5 | const sfNeighborhoods: GeoJSONSourceRaw = { 6 | type: 'geojson', 7 | data: 'https://raw.githubusercontent.com/uber/react-map-gl/master/examples/.data/feature-example-sf.json' 8 | }; 9 | 10 | const fillLayer: FillLayer = { 11 | id: 'sf-neighborhoods-fill', 12 | source: 'sf-neighborhoods', 13 | type: 'fill', 14 | paint: { 15 | 'fill-outline-color': '#0040c8', 16 | 'fill-color': '#fff', 17 | 'fill-opacity': 0 18 | } 19 | }; 20 | 21 | const lineLayer: LineLayer = { 22 | id: 'sf-neighborhoods-outline', 23 | source: 'sf-neighborhoods', 24 | type: 'line', 25 | paint: { 26 | 'line-width': 2, 27 | 'line-color': '#0080ef' 28 | } 29 | }; 30 | 31 | // Make a copy of the map style 32 | export default { 33 | ...MAP_STYLE, 34 | sources: { 35 | ...MAP_STYLE.sources, 36 | ['sf-neighborhoods']: sfNeighborhoods 37 | }, 38 | layers: [...MAP_STYLE.layers, fillLayer, lineLayer] 39 | }; 40 | -------------------------------------------------------------------------------- /src/components/attribution-control.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useEffect, memo} from 'react'; 3 | import {applyReactStyle} from '../utils/apply-react-style'; 4 | import useControl from './use-control'; 5 | 6 | import type {ControlPosition, AttributionControlInstance} from '../types'; 7 | 8 | export type AttributionControlProps = OptionsT & { 9 | /** Placement of the control relative to the map. */ 10 | position?: ControlPosition; 11 | /** CSS style override, applied to the control's container */ 12 | style?: React.CSSProperties; 13 | }; 14 | 15 | function AttributionControl( 16 | props: AttributionControlProps 17 | ): null { 18 | const ctrl = useControl( 19 | ({mapLib}) => new mapLib.AttributionControl(props) as ControlT, 20 | { 21 | position: props.position 22 | } 23 | ); 24 | 25 | useEffect(() => { 26 | applyReactStyle(ctrl._container, props.style); 27 | }, [props.style]); 28 | 29 | return null; 30 | } 31 | 32 | export default memo(AttributionControl); 33 | -------------------------------------------------------------------------------- /examples/viewport-animation/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useRef, useCallback} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map, {MapRef} from 'react-map-gl'; 5 | 6 | import ControlPanel from './control-panel'; 7 | 8 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 9 | 10 | const initialViewState = { 11 | latitude: 37.7751, 12 | longitude: -122.4193, 13 | zoom: 11, 14 | bearing: 0, 15 | pitch: 0 16 | }; 17 | 18 | export default function App() { 19 | const mapRef = useRef(); 20 | 21 | const onSelectCity = useCallback(({longitude, latitude}) => { 22 | mapRef.current?.flyTo({center: [longitude, latitude], duration: 2000}); 23 | }, []); 24 | 25 | return ( 26 | <> 27 | 33 | 34 | 35 | ); 36 | } 37 | 38 | export function renderToDom(container) { 39 | createRoot(container).render(); 40 | } 41 | -------------------------------------------------------------------------------- /examples/clusters/src/layers.ts: -------------------------------------------------------------------------------- 1 | import type {LayerProps} from 'react-map-gl'; 2 | 3 | export const clusterLayer: LayerProps = { 4 | id: 'clusters', 5 | type: 'circle', 6 | source: 'earthquakes', 7 | filter: ['has', 'point_count'], 8 | paint: { 9 | 'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'], 10 | 'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40] 11 | } 12 | }; 13 | 14 | export const clusterCountLayer: LayerProps = { 15 | id: 'cluster-count', 16 | type: 'symbol', 17 | source: 'earthquakes', 18 | filter: ['has', 'point_count'], 19 | layout: { 20 | 'text-field': '{point_count_abbreviated}', 21 | 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'], 22 | 'text-size': 12 23 | } 24 | }; 25 | 26 | export const unclusteredPointLayer: LayerProps = { 27 | id: 'unclustered-point', 28 | type: 'circle', 29 | source: 'earthquakes', 30 | filter: ['!', ['has', 'point_count']], 31 | paint: { 32 | 'circle-color': '#11b4da', 33 | 'circle-radius': 4, 34 | 'circle-stroke-width': 1, 35 | 'circle-stroke-color': '#fff' 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /examples/clusters/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 32 | 33 | 34 |
35 | 36 | 40 | 41 | -------------------------------------------------------------------------------- /examples/controls/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 32 | 33 | 34 |
35 | 36 | 40 | 41 | -------------------------------------------------------------------------------- /examples/terrain/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /examples/geojson-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 32 | 33 | 34 |
35 | 36 | 40 | 41 | -------------------------------------------------------------------------------- /examples/side-by-side/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /examples/viewport-animation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 32 | 33 | 34 |
35 | 36 | 40 | 41 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /examples/draggable-markers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /examples/geojson/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel(props) { 4 | const {year} = props; 5 | 6 | return ( 7 |
8 |

Interactive GeoJSON

9 |

10 | Map showing median household income by state in year {year}. Hover over a state to 11 | see details. 12 |

13 |

14 | Data source: US Census Bureau 15 |

16 | 24 |
25 | 26 |
27 | 28 | props.onChange(evt.target.value)} 35 | /> 36 |
37 |
38 | ); 39 | } 40 | 41 | export default React.memo(ControlPanel); 42 | -------------------------------------------------------------------------------- /examples/viewport-animation/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import CITIES from '../../.data/cities.json'; 4 | 5 | function ControlPanel(props) { 6 | return ( 7 |
8 |

Camera Transition

9 |

Smooth animate of the viewport.

10 | 18 |
19 | 20 | {CITIES.filter(city => city.state === 'California').map((city, index) => ( 21 |
22 | props.onSelectCity(city)} 28 | /> 29 | 30 |
31 | ))} 32 |
33 | ); 34 | } 35 | 36 | export default React.memo(ControlPanel); 37 | -------------------------------------------------------------------------------- /docs/table-of-contents.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "category", 4 | "label": "Overview", 5 | "items": [ 6 | "README", 7 | "whats-new", 8 | "upgrade-guide", 9 | "contributing" 10 | ] 11 | }, 12 | { 13 | "type": "category", 14 | "label": "Developer Guide", 15 | "items": [ 16 | "get-started/get-started", 17 | "get-started/mapbox-tokens", 18 | "get-started/state-management", 19 | "get-started/adding-custom-data", 20 | "get-started/tips-and-tricks" 21 | ] 22 | }, 23 | { 24 | "type": "category", 25 | "label": "API Reference", 26 | "items": [ 27 | "api-reference/map", 28 | "api-reference/attribution-control", 29 | "api-reference/fullscreen-control", 30 | "api-reference/geolocate-control", 31 | "api-reference/layer", 32 | "api-reference/map-provider", 33 | "api-reference/marker", 34 | "api-reference/navigation-control", 35 | "api-reference/popup", 36 | "api-reference/scale-control", 37 | "api-reference/source", 38 | "api-reference/use-control", 39 | "api-reference/use-map", 40 | "api-reference/types" 41 | ] 42 | } 43 | ] -------------------------------------------------------------------------------- /examples/side-by-side/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useCallback} from 'react'; 3 | 4 | export type Mode = 'side-by-side' | 'split-screen'; 5 | 6 | function ControlPanel(props: {mode: Mode; onModeChange: (newMode: Mode) => void}) { 7 | const onModeChange = useCallback( 8 | evt => { 9 | props.onModeChange(evt.target.value as Mode); 10 | }, 11 | [props.onModeChange] 12 | ); 13 | 14 | return ( 15 |
16 |

Side by Side

17 |

Synchronize two maps.

18 | 19 |
20 | 21 | 25 |
26 | 27 | 35 |
36 | ); 37 | } 38 | 39 | export default React.memo(ControlPanel); 40 | -------------------------------------------------------------------------------- /scripts/update-release-branch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Example: 3 | # update-release-branch.sh 6.3 4 | 5 | set -e 6 | 7 | BRANCH=`echo "$1-release"` 8 | VERSION=`echo "$1.0"` 9 | 10 | echo "Updating branch to ${BRANCH}..." 11 | 12 | # Replace source links in docs and examples 13 | find docs -iname "*.md" -type f -exec sed -i '' -E "s/react-map-gl\/(tree|blob)\/(master|[0-9\.]+-release)/react-map-gl\/tree\/${BRANCH}/g" {} \; 14 | find examples -maxdepth 0 -iname "*.md" -type f -exec sed -i '' -E "s/react-map-gl\/(tree|blob)\/(master|[0-9\.]+-release)/react-map-gl\/tree\/${BRANCH}/g" {} \; 15 | find examples/*/src -iname "*.js" -type f -exec sed -i '' -E "s/react-map-gl\/(tree|blob)\/(master|[0-9\.]+-release)/react-map-gl\/tree\/${BRANCH}/g" {} \; 16 | 17 | # Bump dependencies in examples 18 | update_dep() { 19 | FILE=$1 20 | VERSION=$2 21 | cat $FILE | jq ".dependencies |= . + \ 22 | with_entries(select(.key | match(\"react-map-gl\")) | .value |= \"^${VERSION}\")" > temp 23 | mv temp $FILE 24 | } 25 | 26 | # https://stackoverflow.com/questions/4321456/find-exec-a-shell-function-in-linux 27 | export -f update_dep 28 | find examples/*/package.json -exec bash -c 'update_dep "$0" $1' {} $VERSION \; 29 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-map-gl-website", 3 | "scripts": { 4 | "docusaurus": "docusaurus", 5 | "start": "docusaurus start", 6 | "build": "docusaurus clear && npm run write-heading-ids && docusaurus build", 7 | "swizzle": "docusaurus swizzle", 8 | "clear": "docusaurus clear", 9 | "serve": "docusaurus serve", 10 | "write-heading-ids": "ts-node ./write-heading-ids.ts" 11 | }, 12 | "dependencies": { 13 | "@mapbox/mapbox-gl-draw": "^1.3.0", 14 | "@mapbox/mapbox-gl-geocoder": "^4.7.4", 15 | "@turf/area": "^6.0.1", 16 | "@turf/bbox": "^6.0.1", 17 | "d3-array": "^1.0.5", 18 | "d3-scale": "^1.0.6", 19 | "immutable": "^3.7.5", 20 | "react": "^18.0.0", 21 | "react-dom": "^18.0.0", 22 | "styled-components": "^4.3.2" 23 | }, 24 | "devDependencies": { 25 | "@docusaurus/core": "^2.2.0", 26 | "@docusaurus/plugin-content-docs": "^2.2.0", 27 | "@docusaurus/preset-classic": "^2.2.0", 28 | "@easyops-cn/docusaurus-search-local": "^0.35.0", 29 | "@mdx-js/react": "^1.6.22", 30 | "babel-plugin-styled-components": "^2.0.0", 31 | "prism-react-renderer": "^1.2.1", 32 | "ts-node": "~10.9.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/draw-polygon/src/draw-control.ts: -------------------------------------------------------------------------------- 1 | import MapboxDraw from '@mapbox/mapbox-gl-draw'; 2 | import {useControl} from 'react-map-gl'; 3 | 4 | import type {ControlPosition} from 'react-map-gl'; 5 | 6 | type DrawControlProps = ConstructorParameters[0] & { 7 | position?: ControlPosition; 8 | 9 | onCreate?: (evt: {features: object[]}) => void; 10 | onUpdate?: (evt: {features: object[]; action: string}) => void; 11 | onDelete?: (evt: {features: object[]}) => void; 12 | }; 13 | 14 | export default function DrawControl(props: DrawControlProps) { 15 | useControl( 16 | () => new MapboxDraw(props), 17 | ({map}) => { 18 | map.on('draw.create', props.onCreate); 19 | map.on('draw.update', props.onUpdate); 20 | map.on('draw.delete', props.onDelete); 21 | }, 22 | ({map}) => { 23 | map.off('draw.create', props.onCreate); 24 | map.off('draw.update', props.onUpdate); 25 | map.off('draw.delete', props.onDelete); 26 | }, 27 | { 28 | position: props.position 29 | } 30 | ); 31 | 32 | return null; 33 | } 34 | 35 | DrawControl.defaultProps = { 36 | onCreate: () => {}, 37 | onUpdate: () => {}, 38 | onDelete: () => {} 39 | }; 40 | -------------------------------------------------------------------------------- /examples/filter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 38 | 39 | 40 |
41 | 42 | 46 | 47 | -------------------------------------------------------------------------------- /examples/draggable-markers/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type {LngLat} from 'react-map-gl'; 3 | 4 | const eventNames = ['onDragStart', 'onDrag', 'onDragEnd']; 5 | 6 | function round5(value) { 7 | return (Math.round(value * 1e5) / 1e5).toFixed(5); 8 | } 9 | 10 | function ControlPanel(props: {events: Record}) { 11 | return ( 12 |
13 |

Draggable Marker

14 |

Try dragging the marker to another location.

15 |
16 | {eventNames.map(eventName => { 17 | const {events = {}} = props; 18 | const lngLat = events[eventName]; 19 | return ( 20 |
21 | {eventName}:{' '} 22 | {lngLat ? `${round5(lngLat.lng)}, ${round5(lngLat.lat)}` : null} 23 |
24 | ); 25 | })} 26 |
27 | 35 |
36 | ); 37 | } 38 | 39 | export default React.memo(ControlPanel); 40 | -------------------------------------------------------------------------------- /examples/draw-polygon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /examples/geocoder/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 33 | 34 | 35 |
36 | 37 | 41 | 42 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const {getESLintConfig} = require('ocular-dev-tools/configuration'); 2 | 3 | module.exports = getESLintConfig({ 4 | react: '16.8.2', 5 | overrides: { 6 | parserOptions: { 7 | project: ['./tsconfig.json'], 8 | ecmaVersion: 2020 9 | }, 10 | 11 | rules: { 12 | 'max-depth': ['warn', 4], 13 | complexity: ['warn'], 14 | 'max-statements': ['warn'], 15 | 'callback-return': 0 16 | }, 17 | 18 | overrides: [ 19 | { 20 | files: ['**/*.ts', '**/*.tsx', '**/*.d.ts'], 21 | rules: { 22 | // Gradually enable 23 | '@typescript-eslint/ban-ts-comment': 0, 24 | '@typescript-eslint/ban-types': 0, 25 | '@typescript-eslint/no-unsafe-member-access': 0, 26 | '@typescript-eslint/no-unsafe-assignment': 0, 27 | 'import/named': 0, 28 | '@typescript-eslint/no-empty-function': ['warn', {allow: ['arrowFunctions']}], 29 | '@typescript-eslint/restrict-template-expressions': 0, 30 | '@typescript-eslint/explicit-module-boundary-types': 0, 31 | '@typescript-eslint/no-unsafe-return': 0, 32 | '@typescript-eslint/no-unsafe-call': 0, 33 | '@typescript-eslint/restrict-plus-operands': 0 34 | } 35 | } 36 | ] 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /examples/layers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 41 | 42 | 43 |
44 | 45 | 49 | 50 | -------------------------------------------------------------------------------- /examples/custom-cursor/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 43 | 44 | 45 |
46 | 47 | 51 | 52 | -------------------------------------------------------------------------------- /examples/custom-overlay/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | export const COLORS = ['#2b83ba', '#c7191c', '#c8c8cf']; 4 | 5 | const legendStyle = { 6 | display: 'inline-block', 7 | width: 16, 8 | height: 8, 9 | marginRight: 4 10 | }; 11 | 12 | function ControlPanel() { 13 | return ( 14 |
15 |

US Presidential Election 2016 By County

16 |
17 |
18 |
19 | Democrat 20 |
21 |
22 |
23 | Republican 24 |
25 |
26 |
27 | Independent 28 |
29 |
30 |

31 | Data source: Harvard Dataverse 32 |

33 | 41 |
42 | ); 43 | } 44 | 45 | export default React.memo(ControlPanel); 46 | -------------------------------------------------------------------------------- /examples/interaction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 44 | 45 | 46 |
47 | 48 | 52 | 53 | -------------------------------------------------------------------------------- /test/src/components/controls.spec.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Map, 3 | AttributionControl, 4 | FullscreenControl, 5 | GeolocateControl, 6 | NavigationControl, 7 | ScaleControl 8 | } from 'react-map-gl'; 9 | import * as React from 'react'; 10 | import ReactTestRenderer from 'react-test-renderer'; 11 | import test from 'tape-promise/tape'; 12 | import mapboxMock from '../utils/mapbox-gl-mock'; 13 | 14 | test('Controls', t => { 15 | const renderer = ReactTestRenderer.create(); 16 | renderer.update( 17 | 18 | 19 | 20 | ); 21 | t.ok(renderer.root, 'Rendered '); 22 | renderer.update( 23 | 24 | 25 | 26 | ); 27 | t.ok(renderer.root, 'Rendered '); 28 | renderer.update( 29 | 30 | 31 | 32 | ); 33 | t.ok(renderer.root, 'Rendered '); 34 | renderer.update( 35 | 36 | 37 | 38 | ); 39 | t.ok(renderer.root, 'Rendered '); 40 | renderer.update( 41 | 42 | 43 | 44 | ); 45 | t.ok(renderer.root, 'Rendered '); 46 | 47 | renderer.unmount(); 48 | 49 | t.end(); 50 | }); 51 | -------------------------------------------------------------------------------- /examples/heatmap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 46 | 47 | 48 |
49 | 50 | 54 | 55 | -------------------------------------------------------------------------------- /src/components/fullscreen-control.tsx: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import * as React from 'react'; 3 | import {useEffect, memo} from 'react'; 4 | import {applyReactStyle} from '../utils/apply-react-style'; 5 | import useControl from './use-control'; 6 | 7 | import type {ControlPosition, FullscreenControlInstance} from '../types'; 8 | 9 | export type FullscreenControlProps = Omit & { 10 | /** Id of the DOM element which should be made full screen. By default, the map container 11 | * element will be made full screen. */ 12 | containerId?: string; 13 | /** Placement of the control relative to the map. */ 14 | position?: ControlPosition; 15 | /** CSS style override, applied to the control's container */ 16 | style?: React.CSSProperties; 17 | }; 18 | 19 | function FullscreenControl( 20 | props: FullscreenControlProps 21 | ): null { 22 | const ctrl = useControl( 23 | ({mapLib}) => 24 | new mapLib.FullscreenControl({ 25 | container: props.containerId && document.getElementById(props.containerId) 26 | }) as ControlT, 27 | {position: props.position} 28 | ); 29 | 30 | useEffect(() => { 31 | applyReactStyle(ctrl._controlContainer, props.style); 32 | }, [props.style]); 33 | 34 | return null; 35 | } 36 | 37 | export default memo(FullscreenControl); 38 | -------------------------------------------------------------------------------- /website/ocular-docusaurus-plugin/index.js: -------------------------------------------------------------------------------- 1 | // const util = require('util'); 2 | 3 | /* Print full webpack error; 4 | edit node_modules/react-dev-utils/formatWebpackMessages.js 5 | 6 | ``` 7 | } else if ('message' in message) { 8 | return message.message; 9 | ``` 10 | */ 11 | 12 | module.exports = function ( 13 | context, 14 | opts = { 15 | resolve: {modules: [], alias: {}}, 16 | debug: false, 17 | module: {}, 18 | plugins: [] 19 | } 20 | ) { 21 | return { 22 | name: 'ocular-docusaurus-plugin', 23 | configureWebpack(_config, isServer, utils) { 24 | const {resolve, debug, module, plugins} = opts; 25 | 26 | // Custom merging 27 | if (resolve) { 28 | if (resolve.modules) { 29 | _config.resolve.modules = resolve.modules; 30 | } 31 | Object.assign(_config.resolve.alias, resolve.alias); 32 | } 33 | 34 | // Uncomment to inspect config 35 | // console.log(util.inspect(_config.module, {depth: null})); 36 | 37 | // Symlink docs crash otherwise, see https://github.com/facebook/docusaurus/issues/6257 38 | _config.resolve.symlinks = false; 39 | 40 | if (isServer) { 41 | return { 42 | devtool: debug ? 'eval' : false, 43 | module, 44 | plugins, 45 | node: {__dirname: true} 46 | }; 47 | } 48 | return {module, plugins}; 49 | } 50 | }; 51 | }; 52 | -------------------------------------------------------------------------------- /test/src/components/use-map.spec.jsx: -------------------------------------------------------------------------------- 1 | import {Map, MapProvider, useMap} from 'react-map-gl'; 2 | import * as React from 'react'; 3 | import {create, act} from 'react-test-renderer'; 4 | import test from 'tape-promise/tape'; 5 | import {waitForMapLoad} from '../utils/test-utils'; 6 | import mapboxMock from '../utils/mapbox-gl-mock'; 7 | 8 | test('useMap', async t => { 9 | let app; 10 | let maps; 11 | const mapRef = {current: null}; 12 | 13 | function TestControl() { 14 | maps = useMap(); 15 | return null; 16 | } 17 | 18 | act(() => { 19 | app = create( 20 | 21 | 22 | 23 | 24 | 25 | ); 26 | }); 27 | 28 | await waitForMapLoad(mapRef); 29 | 30 | t.ok(maps.mapA, 'Context has mapA'); 31 | t.ok(maps.mapB, 'Context has mapB'); 32 | 33 | act(() => { 34 | app.update( 35 | 36 | 37 | 38 | 39 | ); 40 | }); 41 | 42 | t.ok(maps.mapA, 'Context has mapA'); 43 | t.notOk(maps.mapB, 'mapB is removed'); 44 | 45 | act(() => { 46 | app.update( 47 | 48 | 49 | 50 | ); 51 | }); 52 | 53 | t.notOk(maps.mapA, 'mapA is removed'); 54 | 55 | app.unmount(); 56 | 57 | t.end(); 58 | }); 59 | -------------------------------------------------------------------------------- /examples/terrain/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import Map, {Source, Layer} from 'react-map-gl'; 4 | 5 | import ControlPanel from './control-panel'; 6 | 7 | import type {SkyLayer} from 'react-map-gl'; 8 | 9 | const TOKEN = ''; // Set your mapbox token here 10 | 11 | const skyLayer: SkyLayer = { 12 | id: 'sky', 13 | type: 'sky', 14 | paint: { 15 | 'sky-type': 'atmosphere', 16 | 'sky-atmosphere-sun': [0.0, 0.0], 17 | 'sky-atmosphere-sun-intensity': 15 18 | } 19 | }; 20 | 21 | export default function App() { 22 | return ( 23 | <> 24 | 37 | 44 | 45 | 46 | 47 | 48 | ); 49 | } 50 | 51 | export function renderToDom(container) { 52 | createRoot(container).render(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/interaction/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState, useCallback} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map from 'react-map-gl'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 8 | 9 | const initialViewState = { 10 | latitude: 37.729, 11 | longitude: -122.36, 12 | zoom: 11, 13 | bearing: 0, 14 | pitch: 50 15 | }; 16 | 17 | export default function App() { 18 | const [settings, setSettings] = useState({ 19 | scrollZoom: true, 20 | boxZoom: true, 21 | dragRotate: true, 22 | dragPan: true, 23 | keyboard: true, 24 | doubleClickZoom: true, 25 | touchZoomRotate: true, 26 | touchPitch: true, 27 | minZoom: 0, 28 | maxZoom: 20, 29 | minPitch: 0, 30 | maxPitch: 85 31 | }); 32 | 33 | const updateSettings = useCallback( 34 | (name, value) => 35 | setSettings(s => ({ 36 | ...s, 37 | [name]: value 38 | })), 39 | [] 40 | ); 41 | 42 | return ( 43 | <> 44 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export function renderToDom(container) { 56 | createRoot(container).render(); 57 | } 58 | -------------------------------------------------------------------------------- /website/src/components/example/examples-index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | // Note: this is internal API and may change in a future release 3 | // https://github.com/facebook/docusaurus/discussions/7457 4 | import {useDocsSidebar} from '@docusaurus/theme-common/internal'; 5 | import useBaseUrl from '@docusaurus/useBaseUrl'; 6 | 7 | import {MainExamples, ExamplesGroup, ExampleCard, ExampleHeader, ExampleTitle} from './styled'; 8 | 9 | function renderItem(item, getThumbnail) { 10 | const imageUrl = useBaseUrl(getThumbnail(item)); 11 | const {label, href} = item; 12 | 13 | return ( 14 | 15 | {label} 16 | 17 | {label} 18 | 19 | 20 | ); 21 | } 22 | 23 | function renderCategory({label, items}, getThumbnail) { 24 | return [ 25 | {label}, 26 | {items.map(item => renderItem(item, getThumbnail))} 27 | ]; 28 | } 29 | 30 | export default function ExamplesIndex({getThumbnail}) { 31 | const sidebar = useDocsSidebar(); 32 | 33 | return ( 34 | 35 | {sidebar.items.map(item => { 36 | if (item.type === 'category') { 37 | return renderCategory(item, getThumbnail); 38 | } 39 | return null; 40 | })} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /.github/workflows/website.yml: -------------------------------------------------------------------------------- 1 | name: website 2 | 3 | on: 4 | push: 5 | branches: 6 | - '*-release' 7 | 8 | jobs: 9 | publish-website: 10 | runs-on: ubuntu-latest 11 | 12 | if: github.repository_owner == 'visgl' 13 | 14 | env: 15 | MapboxAccessToken: ${{ secrets.MAPBOX_ACCESS_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v2.1.1 19 | 20 | - name: Use Node.js 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: '16.x' 24 | 25 | - name: Get version 26 | id: get-version 27 | run: LATEST=$(npm show react-map-gl version | grep -o -E "^[0-9]+\.[0-9]+") && echo "::set-output name=latest::/${LATEST}-release" 28 | 29 | - name: Check version 30 | if: ${{ !endsWith(github.ref, steps.get-version.outputs.latest) }} 31 | run: | 32 | echo "Website is only published from the latest release branch" 33 | 34 | - name: Build website 35 | if: ${{ endsWith(github.ref, steps.get-version.outputs.latest) }} 36 | run: | 37 | yarn bootstrap 38 | cd website 39 | yarn 40 | yarn build 41 | 42 | - name: Deploy 43 | if: ${{ endsWith(github.ref, steps.get-version.outputs.latest) }} 44 | uses: JamesIves/github-pages-deploy-action@3.7.1 45 | with: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | BRANCH: gh-pages 48 | FOLDER: website/build 49 | CLEAN: true 50 | -------------------------------------------------------------------------------- /examples/get-started/redux/controls.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {useCallback, useState, useEffect} from 'react'; 4 | import {useSelector, useDispatch} from 'react-redux'; 5 | 6 | export default function Controls() { 7 | const viewState = useSelector(s => s.viewState); 8 | const dispatch = useDispatch(); 9 | const [inputValue, setInputValue] = useState(''); 10 | const [hasError, setError] = useState(false); 11 | 12 | useEffect(() => { 13 | setInputValue(`${viewState.longitude.toFixed(3)}, ${viewState.latitude.toFixed(3)}`); 14 | setError(false); 15 | }, [viewState]); 16 | 17 | const onChange = useCallback(evt => { 18 | setInputValue(evt.target.value); 19 | }, []); 20 | 21 | const onSubmit = useCallback(() => { 22 | const [lng, lat] = inputValue.split(',').map(Number); 23 | if (Math.abs(lng) <= 180 && Math.abs(lat) <= 85) { 24 | dispatch({ 25 | type: 'setViewState', 26 | payload: {...viewState, longitude: lng, latitude: lat} 27 | }); 28 | setError(false); 29 | } else { 30 | setError(true); 31 | } 32 | }, [inputValue]); 33 | 34 | return ( 35 |
36 | MAP CENTER: 37 | 43 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /scripts/github-release.js: -------------------------------------------------------------------------------- 1 | import {execSync} from 'child_process'; 2 | import {readFileSync} from 'fs'; 3 | 4 | // Get the latest tag 5 | const tag = getGitTag(); 6 | if (!tag) { 7 | console.error('TAG NOT FOUND'); 8 | process.exit(1); 9 | } 10 | 11 | // Parse changelog 12 | const changelog = getReleaseNotes(tag); 13 | if (!changelog) { 14 | console.error('CHANGELOG NOT FOUND'); 15 | process.exit(1); 16 | } 17 | 18 | // Publish release notes to GitHub 19 | // https://docs.github.com/en/rest/reference/repos#create-a-release 20 | const requestBody = { 21 | tag_name: tag, 22 | name: tag, 23 | body: changelog, 24 | prerelease: tag.search(/alpha|beta|rc/) > 0 25 | }; 26 | 27 | console.log(JSON.stringify(requestBody)); 28 | 29 | function getGitTag() { 30 | try { 31 | return execSync('git describe --exact-match HEAD', { 32 | stdio: [null, 'pipe', null], 33 | encoding: 'utf-8' 34 | }).trim(); 35 | } catch (err) { 36 | // not tagged 37 | return null; 38 | } 39 | } 40 | 41 | function getReleaseNotes(version) { 42 | let changelog = readFileSync('CHANGELOG.md', 'utf-8'); 43 | const header = changelog.match(new RegExp(`^##.*\\b${version.replace('v', '')}\\b.*$`, 'm')); 44 | if (!header) { 45 | return null; 46 | } 47 | changelog = changelog.slice(header.index + header[0].length); 48 | const endIndex = changelog.search(/^#/m); 49 | if (endIndex > 0) { 50 | changelog = changelog.slice(0, endIndex); 51 | } 52 | return changelog.trim(); 53 | } 54 | -------------------------------------------------------------------------------- /examples/heatmap/src/map-style.ts: -------------------------------------------------------------------------------- 1 | import type {LayerProps} from 'react-map-gl'; 2 | 3 | const MAX_ZOOM_LEVEL = 9; 4 | 5 | export const heatmapLayer: LayerProps = { 6 | id: 'heatmap', 7 | maxzoom: MAX_ZOOM_LEVEL, 8 | type: 'heatmap', 9 | paint: { 10 | // Increase the heatmap weight based on frequency and property magnitude 11 | 'heatmap-weight': ['interpolate', ['linear'], ['get', 'mag'], 0, 0, 6, 1], 12 | // Increase the heatmap color weight weight by zoom level 13 | // heatmap-intensity is a multiplier on top of heatmap-weight 14 | 'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 0, 1, MAX_ZOOM_LEVEL, 3], 15 | // Color ramp for heatmap. Domain is 0 (low) to 1 (high). 16 | // Begin color ramp at 0-stop with a 0-transparancy color 17 | // to create a blur-like effect. 18 | 'heatmap-color': [ 19 | 'interpolate', 20 | ['linear'], 21 | ['heatmap-density'], 22 | 0, 23 | 'rgba(33,102,172,0)', 24 | 0.2, 25 | 'rgb(103,169,207)', 26 | 0.4, 27 | 'rgb(209,229,240)', 28 | 0.6, 29 | 'rgb(253,219,199)', 30 | 0.8, 31 | 'rgb(239,138,98)', 32 | 0.9, 33 | 'rgb(255,201,101)' 34 | ], 35 | // Adjust the heatmap radius by zoom level 36 | 'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 0, 2, MAX_ZOOM_LEVEL, 20], 37 | // Transition from heatmap to circle layer by zoom level 38 | 'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 7, 1, 9, 0] 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /examples/zoom-to-bounds/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useRef} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map from 'react-map-gl'; 5 | import bbox from '@turf/bbox'; 6 | 7 | import ControlPanel from './control-panel'; 8 | import MAP_STYLE from './map-style'; 9 | 10 | import type {MapboxStyle, MapRef, MapLayerMouseEvent} from 'react-map-gl'; 11 | 12 | const TOKEN = ''; // Set your mapbox token here 13 | 14 | export default function App() { 15 | const mapRef = useRef(); 16 | 17 | const onClick = (event: MapLayerMouseEvent) => { 18 | const feature = event.features[0]; 19 | if (feature) { 20 | // calculate the bounding box of the feature 21 | const [minLng, minLat, maxLng, maxLat] = bbox(feature); 22 | 23 | mapRef.current.fitBounds( 24 | [ 25 | [minLng, minLat], 26 | [maxLng, maxLat] 27 | ], 28 | {padding: 40, duration: 1000} 29 | ); 30 | } 31 | }; 32 | 33 | return ( 34 | <> 35 | 47 | 48 | 49 | ); 50 | } 51 | 52 | export function renderToDom(container) { 53 | createRoot(container).render(); 54 | } 55 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/task_queue.js: -------------------------------------------------------------------------------- 1 | // Generated with 2 | // flow-remove-types ./node_modules/mapbox-gl/src/util/task_queue.js 3 | 4 | import assert from 'assert'; 5 | 6 | class TaskQueue { 7 | constructor() { 8 | this._queue = []; 9 | this._id = 0; 10 | this._cleared = false; 11 | this._currentlyRunning = false; 12 | } 13 | 14 | add(callback) { 15 | const id = ++this._id; 16 | const queue = this._queue; 17 | queue.push({callback, id, cancelled: false}); 18 | return id; 19 | } 20 | 21 | remove(id) { 22 | const running = this._currentlyRunning; 23 | const queue = running ? this._queue.concat(running) : this._queue; 24 | for (const task of queue) { 25 | if (task.id === id) { 26 | task.cancelled = true; 27 | return; 28 | } 29 | } 30 | } 31 | 32 | run(timeStamp = 0) { 33 | assert(!this._currentlyRunning); 34 | const queue = (this._currentlyRunning = this._queue); 35 | 36 | // Tasks queued by callbacks in the current queue should be executed 37 | // on the next run, not the current run. 38 | this._queue = []; 39 | 40 | for (const task of queue) { 41 | if (task.cancelled) continue; 42 | task.callback(timeStamp); 43 | if (this._cleared) break; 44 | } 45 | 46 | this._cleared = false; 47 | this._currentlyRunning = false; 48 | } 49 | 50 | clear() { 51 | if (this._currentlyRunning) { 52 | this._cleared = true; 53 | } 54 | this._queue = []; 55 | } 56 | } 57 | 58 | export default TaskQueue; 59 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/popup.js: -------------------------------------------------------------------------------- 1 | import {Evented} from './evented'; 2 | import LngLat from './lng_lat'; 3 | 4 | export default class Popup extends Evented { 5 | constructor(opts) { 6 | super(); 7 | this.options = opts; 8 | 9 | this._lngLat = null; 10 | this._content = null; 11 | this._classList = new Set(opts.className ? opts.className.trim().split(/\s+/) : []); 12 | } 13 | 14 | addTo(map) { 15 | this._map = map; 16 | map._addMarker(this); 17 | return this; 18 | } 19 | remove() { 20 | this._map._removeMarker(this); 21 | this._map = null; 22 | } 23 | getElement() { 24 | return null; 25 | } 26 | 27 | isOpen() { 28 | return this._map !== null; 29 | } 30 | 31 | getLngLat() { 32 | return this._lngLat; 33 | } 34 | setLngLat(value) { 35 | this._lngLat = LngLat.convert(value); 36 | return this; 37 | } 38 | 39 | setText(text) { 40 | this._content = text; 41 | return this; 42 | } 43 | setHTML(html) { 44 | this._content = html; 45 | return this; 46 | } 47 | setDOMContent(htmlNode) { 48 | this._content = htmlNode; 49 | return this; 50 | } 51 | getMaxWidth() { 52 | return this.options.maxWidth; 53 | } 54 | setMaxWidth(value) { 55 | this.options.maxWidth = value; 56 | return this; 57 | } 58 | setOffset(value) { 59 | this.options.offset = value; 60 | return this; 61 | } 62 | 63 | addClassName(className) { 64 | this._classList.add(className); 65 | } 66 | removeClassName(className) { 67 | this._classList.remove(className); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/scale-control.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useEffect, useRef, memo} from 'react'; 3 | import {applyReactStyle} from '../utils/apply-react-style'; 4 | import useControl from './use-control'; 5 | 6 | import type {ControlPosition, ScaleControlInstance} from '../types'; 7 | 8 | export type ScaleControlProps = OptionsT & { 9 | // These props will be further constraint by OptionsT 10 | unit?: string; 11 | maxWidth?: number; 12 | 13 | /** Placement of the control relative to the map. */ 14 | position?: ControlPosition; 15 | /** CSS style override, applied to the control's container */ 16 | style?: React.CSSProperties; 17 | }; 18 | 19 | function ScaleControl( 20 | props: ScaleControlProps 21 | ): null { 22 | const ctrl = useControl(({mapLib}) => new mapLib.ScaleControl(props) as ControlT, { 23 | position: props.position 24 | }); 25 | const propsRef = useRef>(props); 26 | 27 | const prevProps = propsRef.current; 28 | propsRef.current = props; 29 | 30 | const {style} = props; 31 | 32 | if (props.maxWidth !== undefined && props.maxWidth !== prevProps.maxWidth) { 33 | ctrl.options.maxWidth = props.maxWidth; 34 | } 35 | if (props.unit !== undefined && props.unit !== prevProps.unit) { 36 | ctrl.setUnit(props.unit); 37 | } 38 | 39 | useEffect(() => { 40 | applyReactStyle(ctrl._container, style); 41 | }, [style]); 42 | 43 | return null; 44 | } 45 | 46 | export default memo(ScaleControl); 47 | -------------------------------------------------------------------------------- /examples/get-started/hook/controls.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {useCallback, useState, useEffect} from 'react'; 4 | import {useMap} from 'react-map-gl'; 5 | 6 | export default function Controls() { 7 | const {mymap} = useMap(); 8 | const [inputValue, setInputValue] = useState(''); 9 | const [hasError, setError] = useState(false); 10 | 11 | useEffect(() => { 12 | if (!mymap) { 13 | return undefined; 14 | } 15 | 16 | const onMove = () => { 17 | const {lng, lat} = mymap.getCenter(); 18 | setInputValue(`${lng.toFixed(3)}, ${lat.toFixed(3)}`); 19 | setError(false); 20 | }; 21 | mymap.on('move', onMove); 22 | onMove(); 23 | 24 | return () => { 25 | mymap.off('move', onMove); 26 | }; 27 | }, [mymap]); 28 | 29 | const onChange = useCallback(evt => { 30 | setInputValue(evt.target.value); 31 | }, []); 32 | 33 | const onSubmit = useCallback(() => { 34 | const [lng, lat] = inputValue.split(',').map(Number); 35 | if (Math.abs(lng) <= 180 && Math.abs(lat) <= 85) { 36 | mymap.easeTo({ 37 | center: [lng, lat], 38 | duration: 1000 39 | }); 40 | } else { 41 | setError(true); 42 | } 43 | }, [mymap, inputValue]); 44 | 45 | return ( 46 |
47 | MAP CENTER: 48 | 54 | 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /src/types/style-spec-mapbox.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Mapbox Style Specification types 3 | */ 4 | // Layers 5 | import type { 6 | BackgroundLayer, 7 | SkyLayer, 8 | CircleLayer, 9 | FillLayer, 10 | FillExtrusionLayer, 11 | HeatmapLayer, 12 | HillshadeLayer, 13 | LineLayer, 14 | RasterLayer, 15 | SymbolLayer 16 | } from 'mapbox-gl'; 17 | 18 | export type AnyLayer = 19 | | BackgroundLayer 20 | | CircleLayer 21 | | FillExtrusionLayer 22 | | FillLayer 23 | | HeatmapLayer 24 | | HillshadeLayer 25 | | LineLayer 26 | | RasterLayer 27 | | SymbolLayer 28 | | SkyLayer; 29 | 30 | export type { 31 | BackgroundLayer, 32 | SkyLayer, 33 | CircleLayer, 34 | FillLayer, 35 | FillExtrusionLayer, 36 | HeatmapLayer, 37 | HillshadeLayer, 38 | LineLayer, 39 | RasterLayer, 40 | SymbolLayer 41 | }; 42 | 43 | // Sources 44 | import type { 45 | GeoJSONSourceRaw, 46 | VideoSourceRaw, 47 | ImageSourceRaw, 48 | VectorSource as VectorSourceRaw, 49 | RasterSource, 50 | CanvasSourceRaw, 51 | RasterDemSource 52 | } from 'mapbox-gl'; 53 | 54 | export type AnySource = 55 | | GeoJSONSourceRaw 56 | | VideoSourceRaw 57 | | ImageSourceRaw 58 | | CanvasSourceRaw 59 | | VectorSourceRaw 60 | | RasterSource 61 | | RasterDemSource; 62 | 63 | export type { 64 | GeoJSONSourceRaw, 65 | VideoSourceRaw, 66 | ImageSourceRaw, 67 | CanvasSourceRaw, 68 | VectorSourceRaw, 69 | RasterSource, 70 | RasterDemSource 71 | }; 72 | 73 | // Other 74 | export type { 75 | Style as MapStyle, 76 | Light, 77 | Fog, 78 | TerrainSpecification as Terrain, 79 | Projection 80 | } from 'mapbox-gl'; 81 | -------------------------------------------------------------------------------- /test/apps/reuse-maps/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState} from 'react'; 3 | import {render} from 'react-dom'; 4 | import Map, {MapProps} from 'react-map-gl'; 5 | 6 | const TOKEN = ''; // Set your mapbox token here 7 | 8 | const CONFIGS: MapProps[] = [ 9 | { 10 | style: {width: '100%', height: '100%'}, 11 | mapStyle: 'mapbox://styles/mapbox/dark-v9', 12 | initialViewState: { 13 | longitude: -122.4, 14 | latitude: 37.8, 15 | zoom: 12 16 | } 17 | }, 18 | { 19 | style: {width: 400, height: 300, margin: 100}, 20 | mapStyle: 'mapbox://styles/mapbox/light-v9', 21 | initialViewState: { 22 | bounds: [ 23 | [-125, 35], 24 | [-70, 45] 25 | ] 26 | } 27 | }, 28 | { 29 | style: {width: '50vw', height: '100vh', marginLeft: '50vw'}, 30 | mapStyle: 'mapbox://styles/mapbox/streets-v9', 31 | longitude: -70.4, 32 | latitude: 40.1, 33 | zoom: 6 34 | } 35 | ]; 36 | 37 | export default function App() { 38 | const [key, setKey] = useState(0); 39 | const [showMap, setShowMap] = useState(true); 40 | 41 | const onClickBtn = () => { 42 | if (!showMap) { 43 | setKey((key + 1) % CONFIGS.length); 44 | } 45 | setShowMap(!showMap); 46 | }; 47 | 48 | const onLoad = () => console.log(key, 'loaded'); // eslint-disable-line 49 | 50 | return ( 51 | <> 52 | {showMap && } 53 | 54 | 55 | ); 56 | } 57 | 58 | export function renderToDom(container) { 59 | render(, container); 60 | } 61 | -------------------------------------------------------------------------------- /examples/geojson/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 55 | 56 | 57 |
58 | 59 | 63 | 64 | -------------------------------------------------------------------------------- /src/utils/deep-equal.ts: -------------------------------------------------------------------------------- 1 | import type {PointLike} from '../types'; 2 | 3 | /** 4 | * Compare two points 5 | * @param a 6 | * @param b 7 | * @returns true if the points are equal 8 | */ 9 | export function arePointsEqual(a?: PointLike, b?: PointLike): boolean { 10 | const ax = Array.isArray(a) ? a[0] : a ? a.x : 0; 11 | const ay = Array.isArray(a) ? a[1] : a ? a.y : 0; 12 | const bx = Array.isArray(b) ? b[0] : b ? b.x : 0; 13 | const by = Array.isArray(b) ? b[1] : b ? b.y : 0; 14 | return ax === bx && ay === by; 15 | } 16 | 17 | /* eslint-disable complexity */ 18 | /** 19 | * Compare any two objects 20 | * @param a 21 | * @param b 22 | * @returns true if the objects are deep equal 23 | */ 24 | export function deepEqual(a: any, b: any): boolean { 25 | if (a === b) { 26 | return true; 27 | } 28 | if (!a || !b) { 29 | return false; 30 | } 31 | if (Array.isArray(a)) { 32 | if (!Array.isArray(b) || a.length !== b.length) { 33 | return false; 34 | } 35 | for (let i = 0; i < a.length; i++) { 36 | if (!deepEqual(a[i], b[i])) { 37 | return false; 38 | } 39 | } 40 | return true; 41 | } else if (Array.isArray(b)) { 42 | return false; 43 | } 44 | if (typeof a === 'object' && typeof b === 'object') { 45 | const aKeys = Object.keys(a); 46 | const bKeys = Object.keys(b); 47 | if (aKeys.length !== bKeys.length) { 48 | return false; 49 | } 50 | for (const key of aKeys) { 51 | if (!b.hasOwnProperty(key)) { 52 | return false; 53 | } 54 | if (!deepEqual(a[key], b[key])) { 55 | return false; 56 | } 57 | } 58 | return true; 59 | } 60 | return false; 61 | } 62 | -------------------------------------------------------------------------------- /examples/custom-overlay/src/custom-overlay.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState, cloneElement} from 'react'; 3 | import {useControl} from 'react-map-gl'; 4 | import {createPortal} from 'react-dom'; 5 | 6 | import type {IControl, MapInstance} from 'react-map-gl'; 7 | 8 | // Based on template in https://docs.mapbox.com/mapbox-gl-js/api/markers/#icontrol 9 | class OverlayControl implements IControl { 10 | _map: MapInstance = null; 11 | _container: HTMLElement; 12 | _redraw: () => void; 13 | 14 | constructor(redraw: () => void) { 15 | this._redraw = redraw; 16 | } 17 | 18 | onAdd(map) { 19 | this._map = map; 20 | map.on('move', this._redraw); 21 | /* global document */ 22 | this._container = document.createElement('div'); 23 | this._redraw(); 24 | return this._container; 25 | } 26 | 27 | onRemove() { 28 | this._container.remove(); 29 | this._map.off('move', this._redraw); 30 | this._map = null; 31 | } 32 | 33 | getMap() { 34 | return this._map; 35 | } 36 | 37 | getElement() { 38 | return this._container; 39 | } 40 | } 41 | 42 | /** 43 | * A custom control that rerenders arbitrary React content whenever the camera changes 44 | */ 45 | function CustomOverlay(props: {children: React.ReactElement}) { 46 | const [, setVersion] = useState(0); 47 | 48 | const ctrl = useControl(() => { 49 | const forceUpdate = () => setVersion(v => v + 1); 50 | return new OverlayControl(forceUpdate); 51 | }); 52 | 53 | const map = ctrl.getMap(); 54 | 55 | return map && createPortal(cloneElement(props.children, {map}), ctrl.getElement()); 56 | } 57 | 58 | export default React.memo(CustomOverlay); 59 | -------------------------------------------------------------------------------- /examples/custom-overlay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | react-map-gl Example 6 | 7 | 8 | 47 | 48 | 49 |
50 | 51 | 55 | 56 | -------------------------------------------------------------------------------- /examples/geojson-animation/src/app.tsx: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import * as React from 'react'; 3 | import {useState, useEffect} from 'react'; 4 | import {createRoot} from 'react-dom/client'; 5 | import {Map, Source, Layer} from 'react-map-gl'; 6 | import type {LayerProps} from 'react-map-gl'; 7 | 8 | import ControlPanel from './control-panel'; 9 | 10 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 11 | 12 | const pointLayer: LayerProps = { 13 | id: 'point', 14 | type: 'circle', 15 | paint: { 16 | 'circle-radius': 10, 17 | 'circle-color': '#007cbf' 18 | } 19 | }; 20 | 21 | function pointOnCircle({center, angle, radius}) { 22 | return { 23 | type: 'Point', 24 | coordinates: [center[0] + Math.cos(angle) * radius, center[1] + Math.sin(angle) * radius] 25 | }; 26 | } 27 | 28 | export default function App() { 29 | const [pointData, setPointData] = useState(null); 30 | 31 | useEffect(() => { 32 | const animation = window.requestAnimationFrame(() => 33 | setPointData(pointOnCircle({center: [-100, 0], angle: Date.now() / 1000, radius: 20})) 34 | ); 35 | return () => window.cancelAnimationFrame(animation); 36 | }); 37 | 38 | return ( 39 | <> 40 | 49 | {pointData && ( 50 | 51 | 52 | 53 | )} 54 | 55 | 56 | 57 | ); 58 | } 59 | 60 | export function renderToDom(container) { 61 | createRoot(container).render(); 62 | } 63 | -------------------------------------------------------------------------------- /test/src/components/source.spec.jsx: -------------------------------------------------------------------------------- 1 | import {Map, Source} from 'react-map-gl'; 2 | import * as React from 'react'; 3 | import {create, act} from 'react-test-renderer'; 4 | import test from 'tape-promise/tape'; 5 | 6 | import {waitForMapLoad} from '../utils/test-utils'; 7 | import mapboxMock from '../utils/mapbox-gl-mock'; 8 | 9 | test('Source/Layer', async t => { 10 | const mapRef = {current: null}; 11 | 12 | const mapStyle = {}; 13 | const geoJSON = { 14 | type: 'Point', 15 | coordinates: [0, 0] 16 | }; 17 | const geoJSON2 = { 18 | type: 'Point', 19 | coordinates: [1, 1] 20 | }; 21 | 22 | let map; 23 | act(() => { 24 | map = create( 25 | 26 | 27 | 28 | ); 29 | }); 30 | await waitForMapLoad(mapRef); 31 | t.ok(mapRef.current.getSource('my-data'), 'Source is added'); 32 | 33 | act(() => 34 | map.update( 35 | 36 | 37 | 38 | ) 39 | ); 40 | await waitForMapLoad(mapRef); 41 | t.ok(mapRef.current.getSource('my-data'), 'Source is added after style change'); 42 | 43 | act(() => 44 | map.update( 45 | 46 | 47 | 48 | ) 49 | ); 50 | t.is(mapRef.current.getSource('my-data').getData(), geoJSON2, 'Source is updated'); 51 | 52 | act(() => map.update()); 53 | await waitForMapLoad(mapRef); 54 | t.notOk(mapRef.current.getSource('my-data'), 'Source is removed'); 55 | 56 | map.unmount(); 57 | 58 | t.end(); 59 | }); 60 | -------------------------------------------------------------------------------- /test/src/utils/mapbox-gl-mock/marker.js: -------------------------------------------------------------------------------- 1 | import {Evented} from './evented'; 2 | import LngLat from './lng_lat'; 3 | 4 | export default class Marker extends Evented { 5 | constructor(opts) { 6 | super(); 7 | this.options = opts; 8 | 9 | this._map = null; 10 | this._lngLat = null; 11 | this._popup = null; 12 | this._element = opts.element || {addEventListener: () => {}}; 13 | } 14 | 15 | addTo(map) { 16 | this._map = map; 17 | map._addMarker(this); 18 | return this; 19 | } 20 | remove() { 21 | this._map._removeMarker(this); 22 | this._map = null; 23 | } 24 | 25 | getElement() { 26 | return this._element; 27 | } 28 | 29 | getLngLat() { 30 | return this._lngLat; 31 | } 32 | setLngLat(value) { 33 | this._lngLat = LngLat.convert(value); 34 | return this; 35 | } 36 | getOffset() { 37 | return this.options.offset; 38 | } 39 | setOffset(value) { 40 | this.options.offset = value; 41 | return this; 42 | } 43 | isDraggable() { 44 | return this.options.draggable; 45 | } 46 | setDraggable(value) { 47 | this.options.draggable = value; 48 | return this; 49 | } 50 | getRotation() { 51 | return this.options.rotation; 52 | } 53 | setRotation(value) { 54 | this.options.rotation = value; 55 | return this; 56 | } 57 | getRotationAlignment() { 58 | return this.options.rotationAlignment; 59 | } 60 | setRotationAlignment(value) { 61 | this.options.rotationAlignment = value; 62 | return this; 63 | } 64 | getPitchAlignment() { 65 | return this.options.pitchAlignment; 66 | } 67 | setPitchAlignment(value) { 68 | this.options.pitchAlignment = value; 69 | return this; 70 | } 71 | getPopup() { 72 | return this._popup; 73 | } 74 | setPopup(value) { 75 | this._popup = value; 76 | return this; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/draw-polygon/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState, useCallback} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map from 'react-map-gl'; 5 | 6 | import DrawControl from './draw-control'; 7 | import ControlPanel from './control-panel'; 8 | 9 | const TOKEN = ''; // Set your mapbox token here 10 | 11 | export default function App() { 12 | const [features, setFeatures] = useState({}); 13 | 14 | const onUpdate = useCallback(e => { 15 | setFeatures(currFeatures => { 16 | const newFeatures = {...currFeatures}; 17 | for (const f of e.features) { 18 | newFeatures[f.id] = f; 19 | } 20 | return newFeatures; 21 | }); 22 | }, []); 23 | 24 | const onDelete = useCallback(e => { 25 | setFeatures(currFeatures => { 26 | const newFeatures = {...currFeatures}; 27 | for (const f of e.features) { 28 | delete newFeatures[f.id]; 29 | } 30 | return newFeatures; 31 | }); 32 | }, []); 33 | 34 | return ( 35 | <> 36 | 45 | 57 | 58 | 59 | 60 | ); 61 | } 62 | 63 | export function renderToDom(container) { 64 | createRoot(container).render(); 65 | } 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Something does not work as expected 3 | title: "[Bug]" 4 | labels: bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for taking the time to report a bug! 10 | You may find answers faster by searching in [the documentation](https://visgl.github.io/react-map-gl/search) and [existing issues](https://github.com/visgl/react-map-gl/issue). 11 | If you are unsure whether it is a bug in your own implementation or the library itself, consider starting a conversation in [Discussions](https://github.com/visgl/react-map-gl/discussions) instead. 12 | - type: textarea 13 | attributes: 14 | label: Description 15 | description: What you're experiencing. 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Expected Behavior 21 | description: What you expect to see. 22 | validations: 23 | required: false 24 | - type: textarea 25 | attributes: 26 | label: Steps to Reproduce 27 | description: | 28 | Providing a Code Sandbox that reproduces the behavior. You can use [this boiler plate](https://codesandbox.io/s/react-map-gl-boiler-plate-717vw) as a starting point. 29 | validations: 30 | required: true 31 | - type: textarea 32 | attributes: 33 | label: Environment 34 | description: | 35 | Example: 36 | - **Framework version**: react-map-gl@7.0.0 37 | - **Map library**: mapbox-gl@2.7.0 38 | - **Browser**: Chrome 98.0 39 | - **OS**: iOS 15.1 40 | value: | 41 | - Framework version: 42 | - Map library: 43 | - Browser: 44 | - OS: 45 | validations: 46 | required: true 47 | - type: textarea 48 | attributes: 49 | label: Logs 50 | description: Check the browser console for any relevant errors or warnings. 51 | validations: 52 | required: false 53 | -------------------------------------------------------------------------------- /examples/custom-cursor/src/app.tsx: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import * as React from 'react'; 3 | import {useState, useCallback} from 'react'; 4 | import {createRoot} from 'react-dom/client'; 5 | import Map, {MapboxStyle} from 'react-map-gl'; 6 | import ControlPanel from './control-panel'; 7 | import MAP_STYLE from '../../map-style-basic-v8.json'; 8 | 9 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 10 | 11 | const initialViewState = { 12 | longitude: -122.48, 13 | latitude: 37.78, 14 | zoom: 15.5, 15 | bearing: 0, 16 | pitch: 0 17 | }; 18 | 19 | export default function App() { 20 | const [cursor, setCursor] = useState('auto'); 21 | const [interactiveLayerIds, setInteractiveLayerIds] = useState(['nonexist']); 22 | 23 | const onInteractiveLayersChange = useCallback(layerFilter => { 24 | setInteractiveLayerIds(MAP_STYLE.layers.map(layer => layer.id).filter(layerFilter)); 25 | }, []); 26 | 27 | const onClick = useCallback(event => { 28 | const feature = event.features && event.features[0]; 29 | 30 | if (feature) { 31 | window.alert(`Clicked layer ${feature.layer.id}`); // eslint-disable-line no-alert 32 | } 33 | }, []); 34 | 35 | const onMouseEnter = useCallback(() => setCursor('pointer'), []); 36 | const onMouseLeave = useCallback(() => setCursor('auto'), []); 37 | 38 | return ( 39 | <> 40 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export function renderToDom(container) { 56 | createRoot(container).render(); 57 | } 58 | -------------------------------------------------------------------------------- /examples/interaction/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const camelPattern = /(^|[A-Z])[a-z]*/g; 4 | function formatSettingName(name) { 5 | return name.match(camelPattern).join(' '); 6 | } 7 | 8 | function Checkbox({name, value, onChange}) { 9 | return ( 10 |
11 | 12 | onChange(name, evt.target.checked)} /> 13 |
14 | ); 15 | } 16 | 17 | function NumericInput({name, value, onChange}) { 18 | return ( 19 |
20 | 21 | onChange(name, Number(evt.target.value))} 25 | /> 26 |
27 | ); 28 | } 29 | 30 | function ControlPanel(props) { 31 | const {settings, onChange} = props; 32 | 33 | const renderSetting = (name, value) => { 34 | switch (typeof value) { 35 | case 'boolean': 36 | return ; 37 | case 'number': 38 | return ; 39 | default: 40 | return null; 41 | } 42 | }; 43 | 44 | return ( 45 |
46 |

Limit Map Interaction

47 |

Turn interactive features off/on.

48 | 56 |
57 | 58 | {Object.keys(settings).map(name => renderSetting(name, settings[name]))} 59 | 60 |
61 |
62 | ); 63 | } 64 | 65 | export default React.memo(ControlPanel); 66 | -------------------------------------------------------------------------------- /website/src/components/example/styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import {isMobile} from '../common'; 3 | 4 | export const ExampleHeader = styled.div` 5 | font: bold 20px/28px var(--ifm-font-family-base); 6 | color: var(--ifm-color-content-secondary); 7 | margin: 0 20px; 8 | border-bottom: 1px solid 20px; 9 | display: inline-block; 10 | padding: 20px 20px 4px 0; 11 | `; 12 | 13 | export const MainExamples = styled.main` 14 | padding: 16px 0; 15 | `; 16 | 17 | export const ExamplesGroup = styled.main` 18 | display: flex; 19 | flex-wrap: wrap; 20 | padding: 16px; 21 | `; 22 | 23 | export const ExampleCard = styled.a` 24 | cursor: pointer; 25 | text-decoration: none; 26 | width: 50%; 27 | max-width: 240px; 28 | line-height: 0; 29 | outline: none; 30 | padding: 4px; 31 | position: relative; 32 | img { 33 | transition-property: filter; 34 | transition-duration: var(--ifm-transition-slow); 35 | transition-timing-function: var(--ifm-transition-timing-default); 36 | } 37 | &:hover { 38 | box-shadow: var(--ifm-global-shadow-md); 39 | } 40 | &:hover img { 41 | filter: contrast(0.2); 42 | } 43 | ${isMobile} { 44 | width: 33%; 45 | min-width: 200px; 46 | } 47 | @media screen and (max-width: 632px) { 48 | width: 50%; 49 | } 50 | `; 51 | 52 | export const ExampleTitle = styled.div` 53 | position: absolute; 54 | display: flex; 55 | justify-content: center; 56 | flex-direction: column; 57 | color: var(--ifm-color-white); 58 | font-size: 1.5em; 59 | text-align: center; 60 | line-height: initial; 61 | width: 90%; 62 | height: 90%; 63 | top: 5%; 64 | left: 5%; 65 | border: solid 1px var(--ifm-color-white); 66 | opacity: 0; 67 | transition-property: opacity; 68 | transition-duration: var(--ifm-transition-slow); 69 | transition-timing-function: var(--ifm-transition-timing-default); 70 | &:hover { 71 | opacity: 1; 72 | } 73 | `; 74 | -------------------------------------------------------------------------------- /src/utils/style-utils.ts: -------------------------------------------------------------------------------- 1 | import {ImmutableLike, MapStyle} from '../types'; 2 | 3 | const refProps = ['type', 'source', 'source-layer', 'minzoom', 'maxzoom', 'filter', 'layout']; 4 | 5 | // Prepare a map style object for diffing 6 | // If immutable - convert to plain object 7 | // Work around some issues in older styles that would fail Mapbox's diffing 8 | export function normalizeStyle( 9 | style: string | MapStyle | ImmutableLike 10 | ): string | MapStyle { 11 | if (!style) { 12 | return null; 13 | } 14 | if (typeof style === 'string') { 15 | return style; 16 | } 17 | if ('toJS' in style) { 18 | style = style.toJS(); 19 | } 20 | if (!style.layers) { 21 | return style; 22 | } 23 | const layerIndex = {}; 24 | 25 | for (const layer of style.layers) { 26 | layerIndex[layer.id] = layer; 27 | } 28 | 29 | const layers = style.layers.map(layer => { 30 | let normalizedLayer: typeof layer = null; 31 | 32 | if ('interactive' in layer) { 33 | normalizedLayer = Object.assign({}, layer); 34 | // Breaks style diffing :( 35 | // @ts-ignore legacy field not typed 36 | delete normalizedLayer.interactive; 37 | } 38 | 39 | // Style diffing doesn't work with refs so expand them out manually before diffing. 40 | // @ts-ignore legacy field not typed 41 | const layerRef = layerIndex[layer.ref]; 42 | if (layerRef) { 43 | normalizedLayer = normalizedLayer || Object.assign({}, layer); 44 | // @ts-ignore 45 | delete normalizedLayer.ref; 46 | // https://github.com/mapbox/mapbox-gl-js/blob/master/src/style-spec/deref.js 47 | for (const propName of refProps) { 48 | if (propName in layerRef) { 49 | normalizedLayer[propName] = layerRef[propName]; 50 | } 51 | } 52 | } 53 | 54 | return normalizedLayer || layer; 55 | }); 56 | 57 | // Do not mutate the style object provided by the user 58 | return {...style, layers}; 59 | } 60 | -------------------------------------------------------------------------------- /examples/custom-cursor/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState, useEffect} from 'react'; 3 | 4 | // Layer id patterns by category 5 | const layerSelector = { 6 | parks: /park/, 7 | buildings: /building/, 8 | roads: /bridge|road|tunnel/, 9 | labels: /label|place|poi/ 10 | }; 11 | 12 | function getLayerFilter(categories, layerId) { 13 | for (const key in categories) { 14 | if (categories[key] && layerSelector[key].test(layerId)) { 15 | return true; 16 | } 17 | } 18 | return false; 19 | } 20 | 21 | function Checkbox({name, value, onChange}) { 22 | return ( 23 |
24 | 25 | onChange(name, evt.target.checked)} /> 26 |
27 | ); 28 | } 29 | 30 | function StyleControls(props) { 31 | const [categories, setCategories] = useState({ 32 | parks: true, 33 | buildings: true, 34 | roads: true, 35 | labels: true 36 | }); 37 | 38 | useEffect(() => { 39 | const filter = layerId => getLayerFilter(categories, layerId); 40 | props.onChange(filter); 41 | }, [categories]); 42 | 43 | const toggleLayer = (name, on) => { 44 | setCategories({...categories, [name]: on}); 45 | }; 46 | 47 | return ( 48 |
49 |

Custom Cursor

50 |

Customize the cursor based on interactivity.

51 | 59 |
60 |

Clickable layers

61 | {Object.keys(layerSelector).map(name => ( 62 | 63 | ))} 64 |
65 | ); 66 | } 67 | 68 | export default React.memo(StyleControls); 69 | -------------------------------------------------------------------------------- /src/components/use-control.ts: -------------------------------------------------------------------------------- 1 | import {useContext, useMemo, useEffect} from 'react'; 2 | import type {IControl, ControlPosition} from '../types'; 3 | import {MapContext} from './map'; 4 | import type {MapContextValue} from './map'; 5 | 6 | type ControlOptions = { 7 | position?: ControlPosition; 8 | }; 9 | 10 | function useControl( 11 | onCreate: (context: MapContextValue) => T, 12 | opts?: ControlOptions 13 | ): T; 14 | 15 | function useControl( 16 | onCreate: (context: MapContextValue) => T, 17 | onRemove: (context: MapContextValue) => void, 18 | opts?: ControlOptions 19 | ): T; 20 | 21 | function useControl( 22 | onCreate: (context: MapContextValue) => T, 23 | onAdd: (context: MapContextValue) => void, 24 | onRemove: (context: MapContextValue) => void, 25 | opts?: ControlOptions 26 | ): T; 27 | 28 | function useControl( 29 | onCreate: (context: MapContextValue) => T, 30 | arg1?: ((context: MapContextValue) => void) | ControlOptions, 31 | arg2?: ((context: MapContextValue) => void) | ControlOptions, 32 | arg3?: ControlOptions 33 | ): T { 34 | const context = useContext(MapContext); 35 | const ctrl = useMemo(() => onCreate(context), []); 36 | 37 | useEffect(() => { 38 | const opts = (arg3 || arg2 || arg1) as ControlOptions; 39 | const onAdd = typeof arg1 === 'function' && typeof arg2 === 'function' ? arg1 : null; 40 | const onRemove = typeof arg2 === 'function' ? arg2 : typeof arg1 === 'function' ? arg1 : null; 41 | 42 | const {map} = context; 43 | if (!map.hasControl(ctrl)) { 44 | map.addControl(ctrl, opts?.position); 45 | if (onAdd) { 46 | onAdd(context); 47 | } 48 | } 49 | 50 | return () => { 51 | if (onRemove) { 52 | onRemove(context); 53 | } 54 | // Map might have been removed (parent effects are destroyed before child ones) 55 | if (map.hasControl(ctrl)) { 56 | map.removeControl(ctrl); 57 | } 58 | }; 59 | }, []); 60 | 61 | return ctrl; 62 | } 63 | 64 | export default useControl; 65 | -------------------------------------------------------------------------------- /examples/clusters/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useRef} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import {Map, Source, Layer} from 'react-map-gl'; 5 | 6 | import ControlPanel from './control-panel'; 7 | import {clusterLayer, clusterCountLayer, unclusteredPointLayer} from './layers'; 8 | 9 | import type {MapRef} from 'react-map-gl'; 10 | import type {GeoJSONSource} from 'mapbox-gl'; 11 | 12 | const MAPBOX_TOKEN = ''; // Set your mapbox token here 13 | 14 | export default function App() { 15 | const mapRef = useRef(null); 16 | 17 | const onClick = event => { 18 | const feature = event.features[0]; 19 | const clusterId = feature.properties.cluster_id; 20 | 21 | const mapboxSource = mapRef.current.getSource('earthquakes') as GeoJSONSource; 22 | 23 | mapboxSource.getClusterExpansionZoom(clusterId, (err, zoom) => { 24 | if (err) { 25 | return; 26 | } 27 | 28 | mapRef.current.easeTo({ 29 | center: feature.geometry.coordinates, 30 | zoom, 31 | duration: 500 32 | }); 33 | }); 34 | }; 35 | 36 | return ( 37 | <> 38 | 50 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export function renderToDom(container) { 69 | createRoot(container).render(); 70 | } 71 | -------------------------------------------------------------------------------- /test/src/components/popup.spec.jsx: -------------------------------------------------------------------------------- 1 | import {Map, Popup} from 'react-map-gl'; 2 | import * as React from 'react'; 3 | import {create, act} from 'react-test-renderer'; 4 | import test from 'tape-promise/tape'; 5 | 6 | import {waitForMapLoad} from '../utils/test-utils'; 7 | import mapboxMock from '../utils/mapbox-gl-mock'; 8 | 9 | test('Popup', async t => { 10 | const mapRef = {current: null}; 11 | 12 | let map; 13 | act(() => { 14 | map = create( 15 | 16 | 17 | You are here 18 | 19 | 20 | ); 21 | }); 22 | 23 | await waitForMapLoad(mapRef); 24 | 25 | const popup = mapRef.current.getMap()._markers[0]; 26 | t.ok(popup, 'Popup is created'); 27 | 28 | const {anchor, offset, maxWidth} = popup.options; 29 | 30 | act(() => { 31 | map.update( 32 | 33 | 34 | You are here 35 | 36 | 37 | ); 38 | }); 39 | 40 | t.is(offset, popup.options.offset, 'offset did not change deeply'); 41 | 42 | act(() => { 43 | map.update( 44 | 45 | 52 | You are here 53 | 54 | 55 | ); 56 | }); 57 | 58 | t.not(offset, popup.options.offset, 'offset is updated'); 59 | t.not(anchor, popup.options.anchor, 'anchor is updated'); 60 | t.not(maxWidth, popup.options.maxWidth, 'maxWidth is updated'); 61 | 62 | act(() => { 63 | map.update( 64 | 65 | 66 | You are here 67 | 68 | 69 | ); 70 | }); 71 | 72 | t.is(popup.options.className, 'classA', 'className is updated'); 73 | 74 | t.end(); 75 | }); 76 | -------------------------------------------------------------------------------- /examples/draggable-markers/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {useState, useCallback} from 'react'; 3 | import {createRoot} from 'react-dom/client'; 4 | import Map, {Marker, NavigationControl} from 'react-map-gl'; 5 | 6 | import ControlPanel from './control-panel'; 7 | import Pin from './pin'; 8 | 9 | import type {MarkerDragEvent, LngLat} from 'react-map-gl'; 10 | 11 | const TOKEN = ''; // Set your mapbox token here 12 | 13 | const initialViewState = { 14 | latitude: 40, 15 | longitude: -100, 16 | zoom: 3.5 17 | }; 18 | 19 | export default function App() { 20 | const [marker, setMarker] = useState({ 21 | latitude: 40, 22 | longitude: -100 23 | }); 24 | const [events, logEvents] = useState>({}); 25 | 26 | const onMarkerDragStart = useCallback((event: MarkerDragEvent) => { 27 | logEvents(_events => ({..._events, onDragStart: event.lngLat})); 28 | }, []); 29 | 30 | const onMarkerDrag = useCallback((event: MarkerDragEvent) => { 31 | logEvents(_events => ({..._events, onDrag: event.lngLat})); 32 | 33 | setMarker({ 34 | longitude: event.lngLat.lng, 35 | latitude: event.lngLat.lat 36 | }); 37 | }, []); 38 | 39 | const onMarkerDragEnd = useCallback((event: MarkerDragEvent) => { 40 | logEvents(_events => ({..._events, onDragEnd: event.lngLat})); 41 | }, []); 42 | 43 | return ( 44 | <> 45 | 50 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | ); 67 | } 68 | 69 | export function renderToDom(container) { 70 | createRoot(container).render(); 71 | } 72 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import {ArcLayer} from '@deck.gl/layers/typed'; 4 | import {DeckProps, PickingInfo} from '@deck.gl/core/typed'; 5 | import {MapboxOverlay} from '@deck.gl/mapbox/typed'; 6 | import {useControl} from 'react-map-gl'; 7 | 8 | import Map, {NavigationControl} from 'react-map-gl'; 9 | 10 | const TOKEN = ''; // Set your mapbox token here 11 | 12 | const initialViewState = { 13 | latitude: 37.78, 14 | longitude: -122.45, 15 | zoom: 12, 16 | pitch: 45 17 | }; 18 | 19 | function DeckGLOverlay(props: DeckProps) { 20 | const deck = useControl(() => new MapboxOverlay(props)); 21 | 22 | deck.setProps(props); 23 | return null; 24 | } 25 | 26 | // Type of elements in https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json 27 | type DataT = { 28 | inbound: number; 29 | outbound: number; 30 | from: { 31 | name: string; 32 | coordinates: [number, number]; 33 | }; 34 | to: { 35 | name: string; 36 | coordinates: [number, number]; 37 | }; 38 | }; 39 | 40 | export default function App() { 41 | const arcLayer = new ArcLayer({ 42 | data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-segments.json', 43 | getSourcePosition: d => d.from.coordinates, 44 | getTargetPosition: d => d.to.coordinates, 45 | getSourceColor: [255, 200, 0], 46 | getTargetColor: [0, 140, 255], 47 | getWidth: 12, 48 | pickable: true, 49 | autoHighlight: true 50 | }); 51 | 52 | return ( 53 | 58 | 59 | 60 | 61 | ); 62 | } 63 | 64 | function getTooltip(info: PickingInfo) { 65 | const d = info.object as DataT; 66 | return d && `${d.from.name} -- ${d.to.name}`; 67 | } 68 | 69 | export function renderToDom(container) { 70 | createRoot(container).render(); 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/set-globals.ts: -------------------------------------------------------------------------------- 1 | export type GlobalSettings = { 2 | /** The map's default API URL for requesting tiles, styles, sprites, and glyphs. */ 3 | baseApiUrl?: string; 4 | /** The maximum number of images (raster tiles, sprites, icons) to load in parallel. 5 | * @default 16 6 | */ 7 | maxParallelImageRequests?: number; 8 | /** The map's RTL text plugin. Necessary for supporting the Arabic and Hebrew languages, which are written right-to-left. */ 9 | RTLTextPlugin?: string | false; 10 | /** Provides an interface for external module bundlers such as Webpack or Rollup to package mapbox-gl's WebWorker into a separate class and integrate it with the library. 11 | Takes precedence over `workerUrl`. */ 12 | workerClass?: any; 13 | /** The number of web workers instantiated on a page with mapbox-gl maps. 14 | * @default 2 15 | */ 16 | workerCount?: number; 17 | /** Provides an interface for loading mapbox-gl's WebWorker bundle from a self-hosted URL. 18 | * This is useful if your site needs to operate in a strict CSP (Content Security Policy) environment 19 | * wherein you are not allowed to load JavaScript code from a Blob URL, which is default behavior. */ 20 | workerUrl?: string; 21 | }; 22 | 23 | const globalSettings = [ 24 | 'baseApiUrl', 25 | 'maxParallelImageRequests', 26 | 'workerClass', 27 | 'workerCount', 28 | 'workerUrl' 29 | ] as const; 30 | 31 | export default function setGlobals(mapLib: any, props: GlobalSettings) { 32 | for (const key of globalSettings) { 33 | if (key in props) { 34 | mapLib[key] = props[key]; 35 | } 36 | } 37 | 38 | const { 39 | RTLTextPlugin = 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js' 40 | } = props; 41 | if ( 42 | RTLTextPlugin && 43 | mapLib.getRTLTextPluginStatus && 44 | mapLib.getRTLTextPluginStatus() === 'unavailable' 45 | ) { 46 | mapLib.setRTLTextPlugin( 47 | RTLTextPlugin, 48 | (error?: Error) => { 49 | if (error) { 50 | // eslint-disable-next-line 51 | console.error(error); 52 | } 53 | }, 54 | true 55 | ); 56 | } 57 | } 58 | --------------------------------------------------------------------------------