├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yml │ ├── config.yml │ └── feature-request.yml ├── SECURITY.md ├── dependabot.yml └── workflows │ ├── deploy-website.yml │ ├── release-please.yml │ ├── test-website.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .ocularrc.js ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── dev-docs └── RFCs │ └── .gitkeep ├── docs ├── .gitignore ├── README.md ├── api-reference │ ├── components │ │ ├── advanced-marker.md │ │ ├── api-provider.md │ │ ├── info-window.md │ │ ├── map-control.md │ │ ├── map.md │ │ ├── marker.md │ │ └── pin.md │ └── hooks │ │ ├── use-api-is-loaded.md │ │ ├── use-api-loading-status.md │ │ ├── use-map.md │ │ └── use-maps-library.md ├── contributing.md ├── get-started.md ├── guides │ ├── deckgl-integration.md │ ├── interacting-with-google-maps-api.md │ └── writing-examples.md ├── table-of-contents.json ├── upgrade-guide.md └── whats-new.md ├── examples ├── README.md ├── _template │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── vite.config.js ├── advanced-marker-interaction │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ ├── data.ts │ │ └── style.css │ └── vite.config.js ├── autocomplete │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── autocomplete-alpha.tsx │ │ ├── autocomplete-classic.tsx │ │ ├── autocomplete-custom-hybrid.tsx │ │ ├── autocomplete-custom.tsx │ │ ├── control-panel.tsx │ │ ├── map-control.tsx │ │ └── map-handler.tsx │ └── vite.config.js ├── basic-map │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── vite.config.js ├── change-map-styles │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ └── map-styles │ │ │ ├── bright-colors.ts │ │ │ └── vitamin-c.ts │ └── vite.config.js ├── custom-marker-clustering │ ├── README.md │ ├── data │ │ ├── README.md │ │ └── castles.json │ ├── index.html │ ├── package.json │ ├── public │ │ └── castle.svg │ ├── src │ │ ├── app.tsx │ │ ├── castles.ts │ │ ├── components │ │ │ ├── castle-svg.tsx │ │ │ ├── clustered-markers.tsx │ │ │ ├── feature-marker.tsx │ │ │ ├── features-cluster-marker.tsx │ │ │ └── info-window-content.tsx │ │ ├── control-panel.tsx │ │ ├── hooks │ │ │ ├── use-map-viewport.ts │ │ │ └── use-supercluster.ts │ │ └── style.css │ └── vite.config.js ├── deckgl-overlay │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ └── deckgl-overlay.ts │ └── vite.config.js ├── directions │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── vite.config.js ├── drawing │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ ├── drawing-example.tsx │ │ ├── types.ts │ │ ├── undo-redo-control.tsx │ │ ├── undo-redo.ts │ │ └── use-drawing-manager.tsx │ └── vite.config.js ├── examples.css ├── extended-component-library │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.css │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── vite.config.js ├── geometry │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── components │ │ │ ├── circle.tsx │ │ │ ├── index.ts │ │ │ ├── polygon.tsx │ │ │ └── polyline.tsx │ │ ├── control-panel.tsx │ │ └── encoded-polygon-data.ts │ └── vite.config.js ├── global.d.ts ├── heatmap │ ├── README.md │ ├── data │ │ └── earthquakes.json │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ ├── earthquakes.ts │ │ └── heatmap.tsx │ └── vite.config.js ├── homepage-header │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ └── app.tsx │ └── vite.config.js ├── map-3d │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ ├── map-3d │ │ │ ├── index.ts │ │ │ ├── map-3d-types.ts │ │ │ ├── map-3d.tsx │ │ │ └── use-map-3d-camera-events.ts │ │ ├── minimap │ │ │ ├── camera-position-marker.css │ │ │ ├── camera-position-marker.tsx │ │ │ ├── estimate-camera-position.ts │ │ │ ├── index.ts │ │ │ ├── minimap.tsx │ │ │ ├── view-center-marker.css │ │ │ └── view-center-marker.tsx │ │ ├── style.css │ │ └── utility-hooks.ts │ ├── tsconfig.json │ ├── types │ │ └── global.d.ts │ └── vite.config.js ├── map-control │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ └── custom-zoom-control.tsx │ └── vite.config.js ├── marker-clustering │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── clustered-tree-markers.tsx │ │ ├── control-panel.tsx │ │ ├── style.css │ │ ├── tree-marker.tsx │ │ ├── trees.json │ │ └── trees.ts │ └── vite.config.js ├── markers-and-infowindows │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ ├── control-panel.tsx │ │ ├── marker-with-infowindow.tsx │ │ └── moving-marker.tsx │ └── vite.config.js ├── multiple-maps │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ │ ├── app.tsx │ │ └── control-panel.tsx │ └── vite.config.js └── vite.config.local.js ├── jest.config.js ├── package-lock.json ├── package.json ├── scripts ├── install-examples.sh └── update-examples.sh ├── src ├── components │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── map.test.tsx.snap │ │ │ └── pin.test.tsx.snap │ │ ├── __utils__ │ │ │ ├── wait-for-mock-instance.ts │ │ │ └── wait-for-spy.ts │ │ ├── advanced-marker.test.tsx │ │ ├── api-provider.test.tsx │ │ ├── info-window.test.tsx │ │ ├── map-control.test.tsx │ │ ├── map.test.tsx │ │ ├── marker.test.tsx │ │ └── pin.test.tsx │ ├── advanced-marker.tsx │ ├── api-provider.tsx │ ├── info-window.tsx │ ├── map-control.tsx │ ├── map │ │ ├── auth-failure-message.tsx │ │ ├── index.tsx │ │ ├── use-deckgl-camera-update.ts │ │ ├── use-map-camera-params.ts │ │ ├── use-map-events.ts │ │ ├── use-map-instance.ts │ │ ├── use-map-options.ts │ │ └── use-tracked-camera-state-ref.ts │ ├── marker.tsx │ └── pin.tsx ├── hooks │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── use-map.test.tsx.snap │ │ ├── __utils__ │ │ │ ├── wait-for-mock-instance.ts │ │ │ └── wait-for-spy.ts │ │ ├── api-loading.test.tsx │ │ └── use-map.test.tsx │ ├── use-api-is-loaded.ts │ ├── use-api-loading-status.ts │ ├── use-dom-event-listener.ts │ ├── use-map.ts │ ├── use-maps-event-listener.ts │ ├── use-maps-library.ts │ └── use-prop-binding.ts ├── index.ts └── libraries │ ├── __mocks__ │ ├── google-maps-api-loader.ts │ └── lib │ │ └── import-library-mock.ts │ ├── __tests__ │ ├── __snapshots__ │ │ └── google-maps-api-loader.test.ts.snap │ └── google-maps-api-loader.test.ts │ ├── api-loading-status.ts │ ├── assert-not-null.ts │ ├── errors.ts │ ├── google-maps-api-loader.ts │ ├── lat-lng-utils.ts │ ├── limit-tilt-range.ts │ ├── set-value-for-styles.ts │ ├── use-callback-ref.tsx │ ├── use-deep-compare-effect.tsx │ └── use-force-update.ts ├── tsconfig.build.json ├── tsconfig.json ├── tsconfig.test.json └── website ├── .eslintignore ├── .gitignore ├── .npmrc ├── babel.config.js ├── docusaurus.config.js ├── ocular-docusaurus-plugin └── index.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── common.jsx │ ├── example │ │ ├── doc-item-component.jsx │ │ ├── examples-index.jsx │ │ └── styled.js │ ├── home │ │ ├── index.jsx │ │ └── styled.js │ └── index.js ├── docs-sidebar.js ├── examples-sidebar.js ├── examples │ ├── advanced-marker-interaction.mdx │ ├── autocomplete.mdx │ ├── basic-map.mdx │ ├── change-map-styles.mdx │ ├── custom-marker-clustering.mdx │ ├── deckgl-overlay.mdx │ ├── directions.mdx │ ├── drawing.mdx │ ├── extended-component-library.mdx │ ├── geometry.mdx │ ├── heatmap.mdx │ ├── index.mdx │ ├── map-3d.mdx │ ├── map-control.mdx │ ├── marker-clustering.mdx │ ├── markers-and-infowindows.mdx │ └── multiple-maps.mdx ├── pages │ └── index.jsx ├── styles.css └── utils │ └── to-kebap-case.ts └── static ├── favicon.ico ├── images ├── examples │ ├── advanced-marker-interaction.jpg │ ├── autocomplete.jpg │ ├── basic-map.jpg │ ├── change-map-styles.jpg │ ├── custom-marker-clustering.jpg │ ├── deckgl-overlay.jpg │ ├── directions.jpg │ ├── drawing.jpg │ ├── extended-component-library.jpg │ ├── geometry.jpg │ ├── heatmap.jpg │ ├── map-3d.jpg │ ├── map-control.jpg │ ├── marker-clustering.jpg │ ├── markers-and-infowindows.jpg │ └── multiple-maps.jpg ├── icon-high-precision.svg ├── icon-layers.svg ├── icon-react.svg ├── visgl-logo-dark.png ├── visgl-logo-light.png └── visgl-logo.png └── scripts └── examples.js /.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-google-maps/discussions 5 | about: Ask generic questions or request help here 6 | -------------------------------------------------------------------------------- /.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 `@vis.gl/react-google-maps` for and how we can make it better. 10 | 11 | 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. 12 | - type: textarea 13 | attributes: 14 | label: Target Use Case 15 | description: How would this benefit you and other developers? 16 | validations: 17 | required: true 18 | - type: textarea 19 | attributes: 20 | label: Proposal 21 | 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. 22 | validations: 23 | required: true 24 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are applied only to the latest release. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you have discovered a security vulnerability in this project, please report it privately. **Do not disclose it as a public issue.** This gives us time to work with you to fix the issue before public exposure, reducing the chance that the exploit will be used before a patch is released. 10 | 11 | Please disclose it at [security advisory](https://github.com/visgl/react-google-maps/security/advisories/new). 12 | 13 | This project is maintained by a team of volunteers on a reasonable-effort basis. As such, please give us at least 90 days to work on a fix before public exposure. 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 2 | version: 2 3 | updates: 4 | - package-ecosystem: 'npm' 5 | labels: ['dependencies'] 6 | directory: '/' 7 | schedule: 8 | interval: 'weekly' 9 | groups: 10 | dependencies: 11 | update-types: 12 | - 'minor' 13 | - 'patch' 14 | 15 | - package-ecosystem: 'npm' 16 | labels: ['dependencies', 'website'] 17 | directory: './website' 18 | schedule: 19 | interval: 'weekly' 20 | groups: 21 | dependencies: 22 | update-types: 23 | - 'minor' 24 | - 'patch' 25 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | 5 | permissions: 6 | contents: write 7 | pull-requests: write 8 | 9 | name: Release Please 10 | 11 | jobs: 12 | release-please: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - id: release 16 | name: Release Please 17 | uses: googleapis/release-please-action@v4 18 | 19 | with: 20 | release-type: node 21 | package-name: '@vis.gl/react-google-maps' 22 | bump-minor-pre-major: true 23 | 24 | # Below are the actions for actual npm publishing when a release-branch was merged. 25 | 26 | - if: ${{ steps.release.outputs.release_created }} 27 | name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - if: ${{ steps.release.outputs.release_created }} 31 | name: Setup Node 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 20 35 | cache: npm 36 | registry-url: 'https://registry.npmjs.org' 37 | 38 | - if: ${{ steps.release.outputs.release_created }} 39 | name: Install Dependencies 40 | run: npm ci 41 | 42 | - if: ${{ steps.release.outputs.release_created }} 43 | name: Publish 44 | # npm publish will trigger the build via the prepack hook 45 | run: npm publish 46 | env: 47 | NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}} 48 | -------------------------------------------------------------------------------- /.github/workflows/test-website.yml: -------------------------------------------------------------------------------- 1 | name: Website Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | name: Build Website 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v4 11 | 12 | - name: Setup node 13 | uses: actions/setup-node@v4 14 | with: 15 | node-version: '20' 16 | cache: npm 17 | cache-dependency-path: ./website/package-lock.json 18 | 19 | - name: Install dependencies 20 | working-directory: ./website 21 | run: npm ci 22 | 23 | - name: Build website 24 | working-directory: ./website 25 | run: | 26 | npm run build 27 | env: 28 | GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }} 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v4 9 | 10 | - name: Setup Node 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: 20 14 | cache: npm 15 | 16 | - name: Install Dependencies 17 | run: npm ci 18 | 19 | - name: Run Unit Tests 20 | run: npm test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | /examples/**/package-lock.json 4 | /examples/**/node_modules 5 | /examples/**/dist 6 | /examples/**/.env* 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | dist 4 | docs 5 | src 6 | tsconfig.json 7 | -------------------------------------------------------------------------------- /.ocularrc.js: -------------------------------------------------------------------------------- 1 | const {resolve} = require('path'); 2 | 3 | const config = { 4 | lint: { 5 | paths: ['src', 'test', 'examples'] 6 | }, 7 | 8 | typescript: { 9 | project: 'tsconfig.json' 10 | }, 11 | 12 | aliases: { 13 | 'react-map-gl/test': resolve('./test'), 14 | 'react-map-gl': resolve('./src') 15 | }, 16 | 17 | browserTest: { 18 | server: {wait: 5000} 19 | }, 20 | 21 | entry: { 22 | test: 'test/node.js', 23 | 'test-browser': 'test/browser.js', 24 | size: ['test/size/all.js', 'test/size/map.js'] 25 | } 26 | }; 27 | 28 | module.exports = config; 29 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .npmignore 4 | CHANGELOG.md 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "bracketSameLine": true, 10 | "arrowParens": "avoid" 11 | } 12 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | vis.gl is an [OpenJS Foundation](https://openjsf.org/) project. Please be mindful of and adhere to the OpenJS Foundation's [Code of Conduct](https://github.com/openjs-foundation/cross-project-council/blob/main/CODE_OF_CONDUCT.md) when contributing to deck.gl. 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Vis.gl contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dev-docs/RFCs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/dev-docs/RFCs/.gitkeep -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | api-reference/web-mercator-viewport.md 2 | -------------------------------------------------------------------------------- /docs/api-reference/components/map-control.md: -------------------------------------------------------------------------------- 1 | # `` Component 2 | 3 | The `MapControl` component can be used to render components into the 4 | control-containers of a map instance. 5 | 6 | The Maps JavaScript API uses a custom layout algorithm for map controls. 7 | While you can add your buttons or whatever controls you need on top of 8 | the map canvas, that isn't much of an option when you need to mix built-in 9 | controls with your own controls. In this case adding your controls to 10 | the map is the best option. 11 | 12 | See [the official documentation on this topic][gmp-custom-ctrl]. 13 | 14 | ## Usage 15 | 16 | You can add as many `MapControl` components as you like to any `Map`, multiple 17 | controls for the same position are possible as well. 18 | 19 | ```tsx 20 | import { 21 | APIProvider, 22 | ControlPosition, 23 | Map, 24 | MapControl 25 | } from '@vis.gl/react-google-maps'; 26 | 27 | const App = () => ( 28 | 29 | 30 | 31 | .. any component here will be added to the control-containers of the 32 | google map instance .. 33 | 34 | 35 | 36 | ); 37 | ``` 38 | 39 | ## Props 40 | 41 | ### Required 42 | 43 | #### `position`: ControlPosition 44 | 45 | The position is specified as one of the values of the `ControlPosition` enum, which 46 | is an exact copy of the [`google.maps.ControlPosition`][gmp-ctrl-pos] type. 47 | 48 | [gmp-custom-ctrl]: https://developers.google.com/maps/documentation/javascript/controls#CustomControls 49 | [gmp-ctrl-pos]: https://developers.google.com/maps/documentation/javascript/controls#ControlPositioning 50 | -------------------------------------------------------------------------------- /docs/api-reference/components/marker.md: -------------------------------------------------------------------------------- 1 | # `` Component 2 | 3 | React component to display a [Marker](https://developers.google.com/maps/documentation/javascript/reference/marker#Marker) instance. 4 | 5 | ## Usage 6 | 7 | ```tsx 8 | import React, {FunctionComponent} from 'react'; 9 | import {APIProvider, Map, Marker} from '@vis.gl/react-google-maps'; 10 | 11 | const App: FunctionComponent> = () => ( 12 | 13 | 14 | 15 | 16 | 17 | ); 18 | export default App; 19 | ``` 20 | 21 | ## Props 22 | 23 | The MarkerProps interface extends the [google.maps.MarkerOptions interface](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions) and includes all possible options available for a Maps JavaScript API Marker. Additionally, it is possible to add different event listeners, e.g. the click event with the `onClick` property. 24 | 25 | ```tsx 26 | interface MarkerProps extends google.maps.MarkerOptions { 27 | onClick?: (e: google.maps.MapMouseEvent) => void; 28 | onDrag?: (e: google.maps.MapMouseEvent) => void; 29 | onDragStart?: (e: google.maps.MapMouseEvent) => void; 30 | onDragEnd?: (e: google.maps.MapMouseEvent) => void; 31 | onMouseOver?: (e: google.maps.MapMouseEvent) => void; 32 | onMouseOut?: (e: google.maps.MapMouseEvent) => void; 33 | } 34 | ``` 35 | 36 | To see a Marker on the Map, the `position` property needs to be set. 37 | -------------------------------------------------------------------------------- /docs/api-reference/components/pin.md: -------------------------------------------------------------------------------- 1 | # `` Component 2 | 3 | The `Pin` component can be used to customize the appearance of an 4 | [`AdvancedMarker`](./advanced-marker.md) component. 5 | 6 | ## Usage 7 | 8 | ```tsx 9 | const CustomizedMarker = () => ( 10 | 11 | 12 | 13 | ); 14 | ``` 15 | 16 | ## Props 17 | 18 | The `PinProps` type mirrors the [`google.maps.PinElementOptions` interface][gmp-pin-element-options] 19 | and includes all possible options available for a Pin Element instance. 20 | 21 | [gmp-pin-element]: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#PinElement 22 | [gmp-pin-element-options]: https://developers.google.com/maps/documentation/javascript/reference/advanced-markers#PinElementOptions 23 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-api-is-loaded.md: -------------------------------------------------------------------------------- 1 | # `useApiIsLoaded` Hook 2 | 3 | React hook to check if the Maps JavaScript API has finished loading. 4 | 5 | ```tsx 6 | import {useApiIsLoaded} from '@vis.gl/react-google-maps'; 7 | 8 | const MyComponent = () => { 9 | const apiIsLoaded = useApiIsLoaded(); 10 | 11 | useEffect(() => { 12 | if (!apiIsLoaded) return; 13 | 14 | // when the maps library is loaded, apiIsLoaded will be true and the API can be 15 | // accessed using the global `google.maps` namespace. 16 | }, [apiIsLoaded]); 17 | 18 | // ... 19 | }; 20 | ``` 21 | 22 | ## Signature 23 | 24 | `useApiIsLoaded(): boolean` 25 | 26 | Returns a boolean indicating if the Maps JavaScript API completed loading. 27 | 28 | ## Source 29 | 30 | [`src/hooks/use-api-is-loaded.ts`][src] 31 | 32 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-api-is-loaded.ts 33 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-api-loading-status.md: -------------------------------------------------------------------------------- 1 | # `useApiLoadingStatus` Hook 2 | 3 | React hook to get the current status of the API Loader. This can be used to react to loading-errors. 4 | 5 | ```tsx 6 | import {useApiLoadingStatus, APILoadingStatus} from '@vis.gl/react-google-maps'; 7 | 8 | const MyComponent = () => { 9 | const status = useApiLoadingStatus(); 10 | 11 | useEffect(() => { 12 | if (status === APILoadingStatus.FAILED) { 13 | console.log(':('); 14 | 15 | return; 16 | } 17 | }, [status]); 18 | 19 | // ... 20 | }; 21 | ``` 22 | 23 | ## Signature 24 | 25 | `useApiLoadingStatus(): APILoadingStatus` 26 | 27 | Returns the current loading-state. 28 | 29 | ## Source 30 | 31 | [`src/hooks/use-api-loading-status.ts`][src] 32 | 33 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-api-loading-status.ts 34 | -------------------------------------------------------------------------------- /docs/api-reference/hooks/use-maps-library.md: -------------------------------------------------------------------------------- 1 | # `useMapsLibrary` Hook 2 | 3 | React hook to get access to the different [Maps JavaScript API libraries][gmp-libraries]. 4 | This is essentially a react-version of the `google.maps.importLibrary` function. 5 | 6 | ```tsx 7 | const MyComponent = () => { 8 | const map = useMap(); 9 | const placesLib = useMapsLibrary('places'); 10 | 11 | useEffect(() => { 12 | if (!placesLib || !map) return; 13 | 14 | const svc = new placesLib.PlacesService(map); 15 | // ... 16 | }, [placesLib, map]); 17 | 18 | // ... 19 | }; 20 | 21 | // Make sure you have wrapped the component tree with the APIProvider 22 | const App = () => ( 23 | 24 | {/* ... */} 25 | 26 | 27 | ); 28 | ``` 29 | 30 | ## Signature 31 | 32 | `useMapsLibrary(name: string): google.maps.XxxLibrary` 33 | 34 | Returns the library object as it is returned by `google.maps.importLibrary`. 35 | 36 | ### Parameters 37 | 38 | #### `name`: string (required) 39 | 40 | The name of the library that should be loaded 41 | 42 | ## Source 43 | 44 | [`src/hooks/use-maps-library.ts`][src] 45 | 46 | [gmp-libraries]: https://developers.google.com/maps/documentation/javascript/libraries 47 | [src]: https://github.com/visgl/react-google-maps/blob/main/src/hooks/use-maps-library.ts 48 | -------------------------------------------------------------------------------- /docs/get-started.md: -------------------------------------------------------------------------------- 1 | # Get Started 2 | 3 | The easiest way to get started is to start with any of the examples in our 4 | [`./examples` folder](https://github.com/visgl/react-google-maps/tree/main/examples). 5 | Each of the examples is a standalone application using a vite development server 6 | that you can copy as a starting point. 7 | 8 | In order for this to work, an [API key for the Google Maps JavaScript API][gmp-get-api-key] 9 | is required. For the examples, this key has to be provided via an environment variable 10 | `GOOGLE_MAPS_API_KEY`, for example by putting your key into a file named `.env` in the 11 | directory: 12 | 13 | ```text title=".env" 14 | GOOGLE_MAPS_API_KEY= 15 | ``` 16 | 17 | Once that is set up, run `npm install` followed by `npm start` to start the development server. 18 | 19 | [gmp-get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 20 | 21 | ## Installation 22 | 23 | The library can be installed from npm: 24 | 25 | ```bash 26 | npm install @vis.gl/react-google-maps 27 | ``` 28 | or 29 | ```bash 30 | yarn add @vis.gl/react-google-maps 31 | ``` 32 | 33 | This module comes with full TypeScript-support out of the box, so no additional module is 34 | required for the typings. 35 | 36 | ## Example 37 | 38 | A minimal example to just render a map looks like this: 39 | 40 | ```tsx title=index.jsx 41 | import React from 'react'; 42 | import {createRoot} from 'react-dom/client'; 43 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 44 | 45 | const App = () => ( 46 | 47 | 54 | 55 | ); 56 | 57 | const root = createRoot(document.querySelector('#app')); 58 | root.render( 59 | 60 | 61 | 62 | ); 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/table-of-contents.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "type": "category", 4 | "label": "Overview", 5 | "items": [ 6 | "README", 7 | "get-started", 8 | "whats-new", 9 | "upgrade-guide", 10 | "contributing" 11 | ] 12 | }, 13 | { 14 | "type": "category", 15 | "label": "Guides", 16 | "items": [ 17 | "guides/interacting-with-google-maps-api", 18 | "guides/deckgl-integration", 19 | "guides/writing-examples" 20 | ] 21 | }, 22 | { 23 | "type": "category", 24 | "label": "API Reference", 25 | "collapsible": true, 26 | "collapsed": true, 27 | "items": [ 28 | { 29 | "type": "category", 30 | "label": "Components", 31 | "collapsible": true, 32 | "collapsed": true, 33 | 34 | "items": [ 35 | "api-reference/components/api-provider", 36 | "api-reference/components/map", 37 | "api-reference/components/map-control", 38 | "api-reference/components/info-window", 39 | "api-reference/components/marker", 40 | "api-reference/components/advanced-marker", 41 | "api-reference/components/pin" 42 | ] 43 | }, 44 | { 45 | "type": "category", 46 | "label": "Hooks", 47 | "collapsible": true, 48 | "collapsed": true, 49 | 50 | "items": [ 51 | "api-reference/hooks/use-map", 52 | "api-reference/hooks/use-maps-library", 53 | "api-reference/hooks/use-api-is-loaded", 54 | "api-reference/hooks/use-api-loading-status" 55 | ] 56 | } 57 | ] 58 | } 59 | ] 60 | -------------------------------------------------------------------------------- /docs/upgrade-guide.md: -------------------------------------------------------------------------------- 1 | # Upgrade Guide 2 | -------------------------------------------------------------------------------- /docs/whats-new.md: -------------------------------------------------------------------------------- 1 | # What's new 2 | 3 | ## react-google-maps v1.0 4 | 5 | Initial public version 6 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples for @vis.gl/react-google-maps 2 | 3 | All of our examples are fully self-contained and can be run as independent 4 | applications, even when the entire directory is copied elsewhere. Just run 5 | `npm install` and `npm start` in any example directory to start it. 6 | 7 | During development of the library, the examples can also be started using 8 | `npm run start-local`, which will run the example with the checked-out 9 | source of the library instead of the package installed via npm. 10 | 11 | All examples also contain a link to a CodeSandbox environment where the 12 | example can be played with right away. 13 | 14 | When browsing the examples on GitHub, you can also just replace `github.com` 15 | with `githubbox.com` in your url while you're in an example directory to 16 | open it in a codesandbox environment. 17 | 18 | More information about how to write examples [can be found in our 19 | documentation][writing-examples]. 20 | 21 | [writing-examples]: https://visgl.github.io/react-google-maps/docs/guides/writing-examples 22 | -------------------------------------------------------------------------------- /examples/_template/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | This is an example to show how to setup a simple Google Maps Map with the `` component of the Google Maps React 4 | library. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/_template/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/_template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/_template/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => ( 11 | 12 | 18 | 19 | 20 | ); 21 | export default App; 22 | 23 | export function renderToDom(container: HTMLElement) { 24 | const root = createRoot(container); 25 | 26 | root.render( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/_template/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Example Template

7 |

8 | Add a brief description of the example here and update the link below 9 |

10 | 11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /examples/_template/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/README.md: -------------------------------------------------------------------------------- 1 | # Advanced Marker interaction example 2 | 3 | This example showcases a classic interaction pattern when dealing with map markers. 4 | It covers hover-, click- and z-index handling as well as modifying the anchor point for an `AdvancedMarker`. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Advanced Marker interaction 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {AdvancedMarkerAnchorPoint} from '@vis.gl/react-google-maps'; 3 | import {AnchorPointName} from './app'; 4 | 5 | interface Props { 6 | anchorPointName: AnchorPointName; 7 | onAnchorPointChange: (anchorPointName: AnchorPointName) => void; 8 | } 9 | 10 | function ControlPanel(props: Props) { 11 | return ( 12 |
13 |

Advanced Marker interaction

14 |

15 | Markers scale on hover and change their color when they are selected by 16 | clicking on them. The default z-index is sorted by latitude. The z-index 17 | hierachy is "hover" on top, then "selected" and then the default 18 | (latitude). 19 |

20 |

21 | The orange dot on the blue markers represents the current anchor point 22 | of the marker. Use the dropdown to change the anchor point and see its 23 | impact. 24 |

25 |

26 | 41 |

42 | 55 |
56 | ); 57 | } 58 | 59 | export default React.memo(ControlPanel); 60 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/src/data.ts: -------------------------------------------------------------------------------- 1 | type MarkerData = Array<{ 2 | id: string; 3 | position: google.maps.LatLngLiteral; 4 | type: 'pin' | 'html'; 5 | zIndex: number; 6 | }>; 7 | 8 | export function getData() { 9 | const data: MarkerData = []; 10 | 11 | // create 50 random markers 12 | for (let index = 0; index < 50; index++) { 13 | data.push({ 14 | id: String(index), 15 | position: {lat: rnd(53.52, 53.63), lng: rnd(9.88, 10.12)}, 16 | zIndex: index, 17 | type: Math.random() < 0.5 ? 'pin' : 'html' 18 | }); 19 | } 20 | 21 | return data; 22 | } 23 | 24 | function rnd(min: number, max: number) { 25 | return Math.random() * (max - min) + min; 26 | } 27 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/src/style.css: -------------------------------------------------------------------------------- 1 | .custom-marker { 2 | transition: all 200ms ease-in-out; 3 | } 4 | 5 | .custom-html-content { 6 | width: 25px; 7 | height: 25px; 8 | transition: all 200ms ease-in-out; 9 | border: 1px solid #ffa700; 10 | border-radius: 4px; 11 | background: #0057e7; 12 | } 13 | 14 | .custom-html-content.selected { 15 | background: #22ccff; 16 | } 17 | 18 | .visualization-marker { 19 | width: 8px; 20 | height: 8px; 21 | background: #ffa700; 22 | border-radius: 50%; 23 | border: 1px solid #0057e7; 24 | } 25 | -------------------------------------------------------------------------------- /examples/advanced-marker-interaction/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/autocomplete/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Autocomplete Examples 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/autocomplete/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "react-widgets": "^5.8.4", 8 | "vite": "^5.0.4" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/autocomplete/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import {APIProvider, ControlPosition, Map} from '@vis.gl/react-google-maps'; 4 | 5 | import ControlPanel from './control-panel'; 6 | import {CustomMapControl} from './map-control'; 7 | import MapHandler from './map-handler'; 8 | 9 | const API_KEY = 10 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 11 | 12 | export type AutocompleteMode = {id: string; label: string}; 13 | 14 | const autocompleteModes: Array = [ 15 | {id: 'classic', label: 'Google Autocomplete Widget'}, 16 | {id: 'custom', label: 'Custom Build'}, 17 | {id: 'custom-hybrid', label: 'Custom w/ Select Widget'} 18 | ]; 19 | 20 | const App = () => { 21 | const [selectedAutocompleteMode, setSelectedAutocompleteMode] = 22 | useState(autocompleteModes[0]); 23 | 24 | const [selectedPlace, setSelectedPlace] = 25 | useState(null); 26 | 27 | return ( 28 | 29 | 35 | 36 | 41 | 42 | 47 | 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default App; 54 | 55 | export function renderToDom(container: HTMLElement) { 56 | const root = createRoot(container); 57 | 58 | root.render(); 59 | } 60 | -------------------------------------------------------------------------------- /examples/autocomplete/src/autocomplete-alpha.tsx: -------------------------------------------------------------------------------- 1 | // NOTE: This requires the alpha version of the Maps JavaScript API and is not yet 2 | // recommended to be used in production applications. We will add this to the example map 3 | // when it reaches GA (General Availability). Treat this as a preview of what's to come. 4 | 5 | import React, {useRef, useEffect, useState} from 'react'; 6 | import {useMapsLibrary} from '@vis.gl/react-google-maps'; 7 | 8 | interface Props { 9 | onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; 10 | } 11 | 12 | // This is an example of the new "PlaceAutocomplete" widget. 13 | // https://developers.google.com/maps/documentation/javascript/place-autocomplete-new 14 | export const PlaceAutocompleteNew = ({onPlaceSelect}: Props) => { 15 | const [placeAutocomplete, setPlaceAutocomplete] = useState(null); 16 | const containerRef = useRef(null); 17 | const places = useMapsLibrary('places'); 18 | 19 | useEffect(() => { 20 | if (!places) return; 21 | // @ts-expect-error Using an alpha feature here. The types are not up to date yet 22 | setPlaceAutocomplete(new places.PlaceAutocompleteElement()); 23 | }, [places]); 24 | 25 | useEffect(() => { 26 | if (!placeAutocomplete) return; 27 | 28 | placeAutocomplete.addEventListener( 29 | 'gmp-placeselect', 30 | // @ts-expect-error This new event has no types yet 31 | async ({place}: {place: google.maps.places.Place}) => { 32 | await place.fetchFields({ 33 | fields: ['displayName', 'formattedAddress', 'location', 'viewport'] 34 | }); 35 | 36 | onPlaceSelect(place.toJSON()); 37 | } 38 | ); 39 | 40 | containerRef.current?.appendChild(placeAutocomplete); 41 | }, [onPlaceSelect, placeAutocomplete]); 42 | 43 | return
; 44 | }; 45 | -------------------------------------------------------------------------------- /examples/autocomplete/src/autocomplete-classic.tsx: -------------------------------------------------------------------------------- 1 | import React, {useRef, useEffect, useState} from 'react'; 2 | import {useMapsLibrary} from '@vis.gl/react-google-maps'; 3 | 4 | interface Props { 5 | onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; 6 | } 7 | 8 | // This is an example of the classic "Place Autocomplete" widget. 9 | // https://developers.google.com/maps/documentation/javascript/place-autocomplete 10 | export const PlaceAutocompleteClassic = ({onPlaceSelect}: Props) => { 11 | const [placeAutocomplete, setPlaceAutocomplete] = 12 | useState(null); 13 | const inputRef = useRef(null); 14 | const places = useMapsLibrary('places'); 15 | 16 | useEffect(() => { 17 | if (!places || !inputRef.current) return; 18 | 19 | const options = { 20 | fields: ['geometry', 'name', 'formatted_address'] 21 | }; 22 | 23 | setPlaceAutocomplete(new places.Autocomplete(inputRef.current, options)); 24 | }, [places]); 25 | 26 | useEffect(() => { 27 | if (!placeAutocomplete) return; 28 | 29 | placeAutocomplete.addListener('place_changed', () => { 30 | onPlaceSelect(placeAutocomplete.getPlace()); 31 | }); 32 | }, [onPlaceSelect, placeAutocomplete]); 33 | 34 | return ( 35 |
36 | 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /examples/autocomplete/src/map-control.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ControlPosition, MapControl} from '@vis.gl/react-google-maps'; 3 | 4 | import {PlaceAutocompleteClassic} from './autocomplete-classic'; 5 | import {AutocompleteCustom} from './autocomplete-custom'; 6 | 7 | import {AutocompleteCustomHybrid} from './autocomplete-custom-hybrid'; 8 | import type {AutocompleteMode} from './app'; 9 | 10 | type CustomAutocompleteControlProps = { 11 | controlPosition: ControlPosition; 12 | selectedAutocompleteMode: AutocompleteMode; 13 | onPlaceSelect: (place: google.maps.places.PlaceResult | null) => void; 14 | }; 15 | 16 | export const CustomMapControl = ({ 17 | controlPosition, 18 | selectedAutocompleteMode, 19 | onPlaceSelect 20 | }: CustomAutocompleteControlProps) => { 21 | const {id} = selectedAutocompleteMode; 22 | 23 | return ( 24 | 25 |
26 | {id === 'classic' && ( 27 | 28 | )} 29 | 30 | {id === 'custom' && ( 31 | 32 | )} 33 | 34 | {id === 'custom-hybrid' && ( 35 | 36 | )} 37 |
38 |
39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /examples/autocomplete/src/map-handler.tsx: -------------------------------------------------------------------------------- 1 | import {useMap} from '@vis.gl/react-google-maps'; 2 | import React, {useEffect} from 'react'; 3 | 4 | interface Props { 5 | place: google.maps.places.PlaceResult | null; 6 | } 7 | 8 | const MapHandler = ({place}: Props) => { 9 | const map = useMap(); 10 | 11 | useEffect(() => { 12 | if (!map || !place) return; 13 | 14 | if (place.geometry?.viewport) { 15 | map.fitBounds(place.geometry?.viewport); 16 | } 17 | }, [map, place]); 18 | 19 | return null; 20 | }; 21 | 22 | export default React.memo(MapHandler); 23 | -------------------------------------------------------------------------------- /examples/autocomplete/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/basic-map/README.md: -------------------------------------------------------------------------------- 1 | # Basic Map Setup Example 2 | 3 | ![image](https://user-images.githubusercontent.com/39244966/208682692-d5b23518-9e51-4a87-8121-29f71e41c777.png) 4 | 5 | This is an example to show how to setup a simple Google map with the `` component. 6 | 7 | ## Google Maps Platform API Key 8 | 9 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 10 | See [the official documentation][get-api-key] on how to create and configure your own key. 11 | 12 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 13 | file named `.env` in the example directory with the following content: 14 | 15 | ```shell title=".env" 16 | GOOGLE_MAPS_API_KEY="" 17 | ``` 18 | 19 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 20 | 21 | ## Development 22 | 23 | Go into the example-directory and run 24 | 25 | ```shell 26 | npm install 27 | ``` 28 | 29 | To start the example with the local library run 30 | 31 | ```shell 32 | npm run start-local 33 | ``` 34 | 35 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 36 | 37 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 38 | -------------------------------------------------------------------------------- /examples/basic-map/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Basic Map Component Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/basic-map/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/basic-map/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | import ControlPanel from './control-panel'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => ( 11 | 12 | 18 | 19 | 20 | ); 21 | export default App; 22 | 23 | export function renderToDom(container: HTMLElement) { 24 | const root = createRoot(container); 25 | 26 | root.render( 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/basic-map/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Basic Map

7 |

8 | The simplest example possible, just rendering a google map with some 9 | settings adjusted. 10 |

11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /examples/basic-map/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/change-map-styles/README.md: -------------------------------------------------------------------------------- 1 | # Map Styling Example 2 | 3 | This is an example to demonstrate changing the style of the `` component using local and cloud-based map styles as well as map types. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | -------------------------------------------------------------------------------- /examples/change-map-styles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Changing MapIDs 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/change-map-styles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/change-map-styles/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import type {MapConfig} from './app'; 3 | 4 | type ControlPanelProps = { 5 | mapConfigs: MapConfig[]; 6 | mapConfigId: string; 7 | onMapConfigIdChange: (id: string) => void; 8 | }; 9 | 10 | function ControlPanel({ 11 | mapConfigs, 12 | mapConfigId, 13 | onMapConfigIdChange 14 | }: ControlPanelProps) { 15 | return ( 16 |
17 |

Change Map Styles

18 |

19 | The Map component can switch between multiple styles, even 20 | between cloud-based and local styles, on the fly. Switching the mapType 21 | is supported as well. 22 |

23 |

24 | Due to the way the Maps API works, a new google.maps.Map{' '} 25 | instance has to be created when changing the mapId. This will affect the 26 | number of paid map-views. 27 |

28 | 29 |
30 | 31 | 40 |
41 | 42 | 55 |
56 | ); 57 | } 58 | 59 | export default React.memo(ControlPanel); 60 | -------------------------------------------------------------------------------- /examples/change-map-styles/src/map-styles/bright-colors.ts: -------------------------------------------------------------------------------- 1 | // Map Style "No label Bright Colors" by Beniamino Nobile 2 | // https://snazzymaps.com/style/127403/no-label-bright-colors 3 | 4 | export default [ 5 | { 6 | featureType: 'all', 7 | elementType: 'all', 8 | stylers: [ 9 | {saturation: '32'}, 10 | {lightness: '-3'}, 11 | {visibility: 'on'}, 12 | {weight: '1.18'} 13 | ] 14 | }, 15 | { 16 | featureType: 'administrative', 17 | elementType: 'labels', 18 | stylers: [{visibility: 'off'}] 19 | }, 20 | { 21 | featureType: 'landscape', 22 | elementType: 'labels', 23 | stylers: [{visibility: 'off'}] 24 | }, 25 | { 26 | featureType: 'landscape.man_made', 27 | elementType: 'all', 28 | stylers: [{saturation: '-70'}, {lightness: '14'}] 29 | }, 30 | { 31 | featureType: 'poi', 32 | elementType: 'labels', 33 | stylers: [{visibility: 'off'}] 34 | }, 35 | { 36 | featureType: 'road', 37 | elementType: 'labels', 38 | stylers: [{visibility: 'off'}] 39 | }, 40 | { 41 | featureType: 'transit', 42 | elementType: 'labels', 43 | stylers: [{visibility: 'off'}] 44 | }, 45 | { 46 | featureType: 'water', 47 | elementType: 'all', 48 | stylers: [{saturation: '100'}, {lightness: '-14'}] 49 | }, 50 | { 51 | featureType: 'water', 52 | elementType: 'labels', 53 | stylers: [{visibility: 'off'}, {lightness: '12'}] 54 | } 55 | ] as google.maps.MapTypeStyle[]; 56 | -------------------------------------------------------------------------------- /examples/change-map-styles/src/map-styles/vitamin-c.ts: -------------------------------------------------------------------------------- 1 | // Map Style "Vitamin C" by Adam Krogh 2 | // https://snazzymaps.com/style/40/vitamin-c 3 | export default [ 4 | { 5 | featureType: 'water', 6 | elementType: 'geometry', 7 | stylers: [{color: '#004358'}] 8 | }, 9 | { 10 | featureType: 'landscape', 11 | elementType: 'geometry', 12 | stylers: [{color: '#1f8a70'}] 13 | }, 14 | { 15 | featureType: 'poi', 16 | elementType: 'geometry', 17 | stylers: [{color: '#1f8a70'}] 18 | }, 19 | { 20 | featureType: 'road.highway', 21 | elementType: 'geometry', 22 | stylers: [{color: '#fd7400'}] 23 | }, 24 | { 25 | featureType: 'road.arterial', 26 | elementType: 'geometry', 27 | stylers: [{color: '#1f8a70'}, {lightness: -20}] 28 | }, 29 | { 30 | featureType: 'road.local', 31 | elementType: 'geometry', 32 | stylers: [{color: '#1f8a70'}, {lightness: -17}] 33 | }, 34 | { 35 | elementType: 'labels.text.stroke', 36 | stylers: [{color: '#ffffff'}, {visibility: 'on'}, {weight: 0.9}] 37 | }, 38 | { 39 | elementType: 'labels.text.fill', 40 | stylers: [{visibility: 'on'}, {color: '#ffffff'}] 41 | }, 42 | { 43 | featureType: 'poi', 44 | elementType: 'labels', 45 | stylers: [{visibility: 'simplified'}] 46 | }, 47 | {elementType: 'labels.icon', stylers: [{visibility: 'off'}]}, 48 | { 49 | featureType: 'transit', 50 | elementType: 'geometry', 51 | stylers: [{color: '#1f8a70'}, {lightness: -10}] 52 | }, 53 | {}, 54 | { 55 | featureType: 'administrative', 56 | elementType: 'geometry', 57 | stylers: [{color: '#1f8a70'}, {weight: 0.7}] 58 | } 59 | ] as google.maps.MapTypeStyle[]; 60 | -------------------------------------------------------------------------------- /examples/change-map-styles/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/README.md: -------------------------------------------------------------------------------- 1 | # Custom Marker Clustering Example 2 | 3 | This example demonstrates how the [Supercluster](https://github.com/mapbox/supercluster) 4 | algorithm can be directly used with `@vis.gl/react-google-maps` to create 5 | 6 | ## Google Maps API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via the environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/data/README.md: -------------------------------------------------------------------------------- 1 | # `castles.json` Data Source 2 | 3 | (tools: `wget`, [`jq`](https://jqlang.github.io/jq/)) 4 | 5 | ## 1. fetch data from overpass-api.de 6 | 7 | ```shell 8 | query="[out:json];nwr[historic=castle][tourism=attraction][name][wikidata];convert item ::=::,::geom=geom(),_osm_type=type(); out center;" 9 | wget -O castles-osm.json "http://overpass-api.de/api/interpreter?data=${query}" 10 | ``` 11 | 12 | ## 2. transform to proper geojson, ditching most tags 13 | 14 | ```shell 15 | jq '{ 16 | type: "FeatureCollection", 17 | features: .elements | map({ 18 | type:"Feature", 19 | id: .tags.wikidata, 20 | geometry: .geometry, 21 | properties: { 22 | name: (.tags["name:en"] // .tags.name), 23 | wikipedia:.tags.wikipedia, 24 | wikidata:.tags.wikidata 25 | } 26 | }) 27 | }' \ 28 | < castles-osm.json \ 29 | > castles.json 30 | ``` 31 | 32 | ## 3. some manual adjustments 33 | 34 | - a couple of duplicate ids removed or changed 35 | - one instance where the wikipedia-tag was broken and didn't contain the 36 | language part 37 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Custom Marker Clustering 9 | 10 | 21 | 22 | 23 |
24 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/markerclusterer": "^2.5.1", 5 | "@types/geojson": "^7946.0.14", 6 | "@types/supercluster": "^7.1.3", 7 | "@vis.gl/react-google-maps": "latest", 8 | "react": "^18.2.0", 9 | "react-dom": "^18.2.0", 10 | "supercluster": "^8.0.1", 11 | "vite": "^5.0.4" 12 | }, 13 | "scripts": { 14 | "start": "vite", 15 | "start-local": "vite --config ../vite.config.local.js", 16 | "build": "vite build" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/public/castle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/castles.ts: -------------------------------------------------------------------------------- 1 | import {FeatureCollection, Point} from 'geojson'; 2 | 3 | export type CastleFeatureProps = { 4 | name: string; 5 | wikipedia: string; 6 | wikidata: string; 7 | }; 8 | 9 | export type CastlesGeojson = FeatureCollection; 10 | 11 | export async function loadCastlesGeojson(): Promise { 12 | const url = new URL('../data/castles.json', import.meta.url); 13 | 14 | return await fetch(url).then(res => res.json()); 15 | } 16 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/castle-svg.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | /** 4 | * @copyright Castle by Rikas Dzihab from Noun Project (CC BY 3.0) 5 | * @link https://thenounproject.com/icon/castle-6703640/ 6 | */ 7 | export const CastleSvg = () => ( 8 | 9 | 13 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/feature-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {AdvancedMarker, useAdvancedMarkerRef} from '@vis.gl/react-google-maps'; 3 | import {CastleSvg} from './castle-svg'; 4 | 5 | type TreeMarkerProps = { 6 | position: google.maps.LatLngLiteral; 7 | featureId: string; 8 | onMarkerClick?: ( 9 | marker: google.maps.marker.AdvancedMarkerElement, 10 | featureId: string 11 | ) => void; 12 | }; 13 | 14 | export const FeatureMarker = ({ 15 | position, 16 | featureId, 17 | onMarkerClick 18 | }: TreeMarkerProps) => { 19 | const [markerRef, marker] = useAdvancedMarkerRef(); 20 | const handleClick = useCallback( 21 | () => onMarkerClick && onMarkerClick(marker!, featureId), 22 | [onMarkerClick, marker, featureId] 23 | ); 24 | 25 | return ( 26 | 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/features-cluster-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback} from 'react'; 2 | import {AdvancedMarker, useAdvancedMarkerRef} from '@vis.gl/react-google-maps'; 3 | import {CastleSvg} from './castle-svg'; 4 | 5 | type TreeClusterMarkerProps = { 6 | clusterId: number; 7 | onMarkerClick?: ( 8 | marker: google.maps.marker.AdvancedMarkerElement, 9 | clusterId: number 10 | ) => void; 11 | position: google.maps.LatLngLiteral; 12 | size: number; 13 | sizeAsText: string; 14 | }; 15 | 16 | export const FeaturesClusterMarker = ({ 17 | position, 18 | size, 19 | sizeAsText, 20 | onMarkerClick, 21 | clusterId 22 | }: TreeClusterMarkerProps) => { 23 | const [markerRef, marker] = useAdvancedMarkerRef(); 24 | const handleClick = useCallback( 25 | () => onMarkerClick && onMarkerClick(marker!, clusterId), 26 | [onMarkerClick, marker, clusterId] 27 | ); 28 | const markerSize = Math.floor(48 + Math.sqrt(size) * 2); 29 | return ( 30 | 37 | 38 | {sizeAsText} 39 | 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/components/info-window-content.tsx: -------------------------------------------------------------------------------- 1 | import React, {memo} from 'react'; 2 | import {Feature, Point} from 'geojson'; 3 | import {CastleFeatureProps} from '../castles'; 4 | 5 | type InfowindowContentProps = { 6 | features: Feature[]; 7 | }; 8 | 9 | const numFmt = new Intl.NumberFormat(); 10 | 11 | export const InfoWindowContent = memo(({features}: InfowindowContentProps) => { 12 | if (features.length === 1) { 13 | const f = features[0]; 14 | const props = f.properties! as CastleFeatureProps; 15 | 16 | return ( 17 |
18 |

{props.name}

19 |

20 | 21 | more information 22 | 23 |

24 |
25 | ); 26 | } 27 | 28 | return ( 29 |
30 |

{numFmt.format(features.length)} features. Zoom in to explore.

31 | 32 |
    33 | {features.slice(0, 5).map(feature => { 34 | const props = feature.properties! as CastleFeatureProps; 35 | 36 | return ( 37 |
  • 38 | 39 | {props.name} 40 | 41 |
  • 42 | ); 43 | })} 44 | 45 | {features.length > 5 && ( 46 |
  • and {numFmt.format(features.length - 5)} more.
  • 47 | )} 48 |
49 |
50 | ); 51 | }); 52 | 53 | function getDetailsUrl(props: CastleFeatureProps) { 54 | return props.wikipedia 55 | ? getWikipediaUrl(props.wikipedia) 56 | : getWikidataUrl(props.wikidata); 57 | } 58 | function getWikipediaUrl(contentId: string) { 59 | const [lang, title] = contentId.split(':'); 60 | 61 | return `https://${lang}.wikipedia.org/wiki/${title.replace(/ /g, '_')}`; 62 | } 63 | function getWikidataUrl(id: string) { 64 | return `https://www.wikidata.org/wiki/${id}`; 65 | } 66 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/hooks/use-map-viewport.ts: -------------------------------------------------------------------------------- 1 | import {useMap} from '@vis.gl/react-google-maps'; 2 | import {useEffect, useState} from 'react'; 3 | import {BBox} from 'geojson'; 4 | 5 | type MapViewportOptions = { 6 | padding?: number; 7 | }; 8 | 9 | export function useMapViewport({padding = 0}: MapViewportOptions = {}) { 10 | const map = useMap(); 11 | const [bbox, setBbox] = useState([-180, -90, 180, 90]); 12 | const [zoom, setZoom] = useState(0); 13 | 14 | // observe the map to get current bounds 15 | useEffect(() => { 16 | if (!map) return; 17 | 18 | const listener = map.addListener('bounds_changed', () => { 19 | const bounds = map.getBounds(); 20 | const zoom = map.getZoom(); 21 | const projection = map.getProjection(); 22 | 23 | if (!bounds || !zoom || !projection) return; 24 | 25 | const sw = bounds.getSouthWest(); 26 | const ne = bounds.getNorthEast(); 27 | 28 | const paddingDegrees = degreesPerPixel(zoom) * padding; 29 | 30 | const n = Math.min(90, ne.lat() + paddingDegrees); 31 | const s = Math.max(-90, sw.lat() - paddingDegrees); 32 | 33 | const w = sw.lng() - paddingDegrees; 34 | const e = ne.lng() + paddingDegrees; 35 | 36 | setBbox([w, s, e, n]); 37 | setZoom(zoom); 38 | }); 39 | 40 | return () => listener.remove(); 41 | }, [map, padding]); 42 | 43 | return {bbox, zoom}; 44 | } 45 | 46 | function degreesPerPixel(zoomLevel: number) { 47 | // 360° divided by the number of pixels at the zoom-level 48 | return 360 / (Math.pow(2, zoomLevel) * 256); 49 | } 50 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/src/style.css: -------------------------------------------------------------------------------- 1 | .custom-marker-clustering-control-panel .attribution { 2 | font-size: 0.75em; 3 | } 4 | 5 | @keyframes appear { 6 | from { 7 | opacity: 0.5; 8 | scale: 0.8; 9 | } 10 | 80% { 11 | scale: 1.05; 12 | } 13 | to { 14 | opacity: 1; 15 | scale: 1; 16 | } 17 | } 18 | 19 | .custom-marker-clustering-map { 20 | width: 100vw; 21 | height: 100vh; 22 | } 23 | 24 | .custom-marker-clustering-map .marker { 25 | box-sizing: border-box; 26 | border-radius: 50%; 27 | padding: 8px; 28 | translate: 0 50%; 29 | border: 1px solid white; 30 | color: white; 31 | 32 | display: flex; 33 | flex-flow: column nowrap; 34 | align-items: center; 35 | 36 | animation: appear 150ms both; 37 | } 38 | 39 | .custom-marker-clustering-map .marker svg { 40 | width: 100%; 41 | vector-effect: non-scaling-stroke; 42 | } 43 | 44 | .custom-marker-clustering-map .marker.feature { 45 | width: 40px; 46 | height: 40px; 47 | 48 | background-color: #3f5b72; 49 | filter: drop-shadow(1px 2px 3px rgba(0, 0, 0, 0.5)); 50 | } 51 | 52 | .custom-marker-clustering-map .marker.cluster { 53 | width: 56px; 54 | height: 56px; 55 | background-color: #618bad; 56 | filter: drop-shadow(2px 4px 12px rgba(0, 0, 0, 0.7)); 57 | overflow: hidden; 58 | padding-bottom: 0; 59 | } 60 | 61 | .custom-marker-clustering-map .marker.cluster span { 62 | background-color: white; 63 | color: rgba(0, 0, 0, 0.5); 64 | padding: 3px 6px; 65 | /* oversized by the padding of the container */ 66 | width: calc(100% + 16px); 67 | text-align: center; 68 | margin-top: 6px; 69 | height: 30px; 70 | } 71 | -------------------------------------------------------------------------------- /examples/custom-marker-clustering/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/README.md: -------------------------------------------------------------------------------- 1 | # deck.gl Interleaved Overlay Example 2 | 3 | An example demonstrating how an interleaved deck.gl overlay can be added 4 | to a `` component. (using the `GoogleMapsOverlay` from [@deck.gl/google-maps][]). 5 | 6 | [@deck.gl/google-maps]: https://deck.gl/docs/api-reference/google-maps/overview 7 | 8 | ## Google Maps Platform API Key 9 | 10 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 11 | See [the official documentation][get-api-key] on how to create and configure your own key. 12 | 13 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 14 | file named `.env` in the example directory with the following content: 15 | 16 | ```shell title=".env" 17 | GOOGLE_MAPS_API_KEY="" 18 | ``` 19 | 20 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 21 | 22 | ## Development 23 | 24 | Go into the example-directory and run 25 | 26 | ```shell 27 | npm install 28 | ``` 29 | 30 | To start the example with the local library run 31 | 32 | ```shell 33 | npm run start-local 34 | ``` 35 | 36 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 37 | 38 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 39 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: deck.gl Interleaved Overlay 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@deck.gl/google-maps": "^8.9.34", 5 | "deck.gl": "^8.9.34", 6 | "@vis.gl/react-google-maps": "latest", 7 | "react": "^18.2.0", 8 | "react-dom": "^18.2.0", 9 | "vite": "^5.0.4" 10 | }, 11 | "scripts": { 12 | "start": "vite", 13 | "start-local": "vite --config ../vite.config.local.js", 14 | "build": "vite build" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | 6 | import {GeoJsonLayer} from '@deck.gl/layers/typed'; 7 | import {DeckGlOverlay} from './deckgl-overlay'; 8 | 9 | const DATA_URL = 10 | 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart.geo.json'; 11 | 12 | import type {Feature, GeoJSON} from 'geojson'; 13 | import ControlPanel from './control-panel'; 14 | 15 | const API_KEY = 16 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 17 | 18 | const App = () => { 19 | const [data, setData] = useState(null); 20 | 21 | useEffect(() => { 22 | fetch(DATA_URL) 23 | .then(res => res.json()) 24 | .then(data => setData(data as GeoJSON)); 25 | }, []); 26 | 27 | return ( 28 | 29 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | function getDeckGlLayers(data: GeoJSON | null) { 43 | if (!data) return []; 44 | 45 | return [ 46 | new GeoJsonLayer({ 47 | id: 'geojson-layer', 48 | data, 49 | stroked: false, 50 | filled: true, 51 | extruded: true, 52 | pointType: 'circle', 53 | lineWidthScale: 20, 54 | lineWidthMinPixels: 4, 55 | getFillColor: [160, 160, 180, 200], 56 | getLineColor: (f: Feature) => { 57 | const hex = f?.properties?.color; 58 | 59 | if (!hex) return [0, 0, 0]; 60 | 61 | return hex.match(/[0-9a-f]{2}/g)!.map((x: string) => parseInt(x, 16)); 62 | }, 63 | getPointRadius: 200, 64 | getLineWidth: 1, 65 | getElevation: 30 66 | }) 67 | ]; 68 | } 69 | 70 | export default App; 71 | 72 | export function renderToDom(container: HTMLElement) { 73 | const root = createRoot(container); 74 | 75 | root.render( 76 | 77 | 78 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

deck.gl Interleaved Overlay Example

7 |

8 | An example demonstrating how an interleaved deck.gl overlay can be added 9 | to a {''} component. (using the{' '} 10 | GoogleMapsOverlay from{' '} 11 | 12 | @deck.gl/google-maps 13 | 14 | ). 15 |

16 | 29 |
30 | ); 31 | } 32 | 33 | export default React.memo(ControlPanel); 34 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/src/deckgl-overlay.ts: -------------------------------------------------------------------------------- 1 | import {useMap} from '@vis.gl/react-google-maps'; 2 | import {useEffect, useMemo} from 'react'; 3 | 4 | import {GoogleMapsOverlay} from '@deck.gl/google-maps/typed'; 5 | 6 | import type {LayersList} from '@deck.gl/core/typed'; 7 | 8 | export type DeckglOverlayProps = {layers?: LayersList}; 9 | 10 | /** 11 | * A very simple implementation of a component that renders a list of deck.gl layers 12 | * via the GoogleMapsOverlay into the component containing it. 13 | */ 14 | export const DeckGlOverlay = ({layers}: DeckglOverlayProps) => { 15 | // the GoogleMapsOverlay can persist throughout the lifetime of the DeckGlOverlay 16 | const deck = useMemo(() => new GoogleMapsOverlay({interleaved: true}), []); 17 | 18 | // add the overlay to the map once the map is available 19 | const map = useMap(); 20 | useEffect(() => { 21 | deck.setMap(map); 22 | 23 | return () => deck.setMap(null); 24 | }, [deck, map]); 25 | 26 | // whenever the rendered data changes, the layers will be updated 27 | useEffect(() => { 28 | deck.setProps({layers}); 29 | }, [deck, layers]); 30 | 31 | // no dom rendered by this component 32 | return null; 33 | }; 34 | -------------------------------------------------------------------------------- /examples/deckgl-overlay/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/directions/README.md: -------------------------------------------------------------------------------- 1 | # Google Maps Directions API Example 2 | 3 | ![image](https://raw.githubusercontent.com/visgl/react-google-maps/main/website/static/images/examples/directions.jpg) 4 | 5 | This is an example which shows how to use `useMapsLibrary` to load the `routes` library, and then use `DirectionsService` and `DirectionsRenderer` to find and display a route on a map. 6 | 7 | It allows the user to choose alternative routes, updating the route being rendered on the map. 8 | 9 | ## Google Maps Platform API Key 10 | 11 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 12 | See [the official documentation][get-api-key] on how to create and configure your own key. 13 | 14 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 15 | file named `.env` in the example directory with the following content: 16 | 17 | ```shell title=".env" 18 | GOOGLE_MAPS_API_KEY="" 19 | ``` 20 | 21 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 22 | 23 | ## Development 24 | 25 | Go into the example-directory and run 26 | 27 | ```shell 28 | npm install 29 | ``` 30 | 31 | To start the example with the local library run 32 | 33 | ```shell 34 | npm run start-local 35 | ``` 36 | 37 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 38 | 39 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 40 | -------------------------------------------------------------------------------- /examples/directions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Directions 9 | 10 | 58 | 59 | 60 |
61 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /examples/directions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/directions/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Directions

7 |

8 | Loading the routes library to render directions on the map using 9 | DirectionsService and DirectionsRenderer. 10 |

11 | 12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/directions/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/drawing/README.md: -------------------------------------------------------------------------------- 1 | # Drawing Tools Example 2 | 3 | This example shows how to use the [google.maps.drawing.DrawingManager][drawing-manager] to draw shapes or markers on the map. In addition the example implements an undo/redo flow for the drawing tools. If you only want to add the the drawing tools to your map take a look at the `use-drawing-manager` hook. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | [drawing-manager]: https://developers.google.com/maps/documentation/javascript/drawinglayer 37 | -------------------------------------------------------------------------------- /examples/drawing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Drawing Tools Example 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/drawing/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/drawing/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import {APIProvider} from '@vis.gl/react-google-maps'; 4 | 5 | import DrawingExample from './drawing-example'; 6 | 7 | const API_KEY = 8 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 9 | 10 | const App = () => { 11 | return ( 12 | 13 | 14 | 15 | ); 16 | }; 17 | 18 | export default App; 19 | 20 | export function renderToDom(container: HTMLElement) { 21 | const root = createRoot(container); 22 | 23 | root.render(); 24 | } 25 | -------------------------------------------------------------------------------- /examples/drawing/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Drawing Tools Example

7 |

8 | Shows how to use the drawing tools of the Maps JavaScript API and 9 | implements an undo/redo flow to show how to integrate the drawing 10 | manager and its events into the state of a react-application. 11 |

12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/drawing/src/drawing-example.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {ControlPosition, Map, MapControl} from '@vis.gl/react-google-maps'; 3 | 4 | import {UndoRedoControl} from './undo-redo-control'; 5 | import {useDrawingManager} from './use-drawing-manager'; 6 | import ControlPanel from './control-panel'; 7 | 8 | const DrawingExample = () => { 9 | const drawingManager = useDrawingManager(); 10 | 11 | return ( 12 | <> 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | 29 | export default DrawingExample; 30 | -------------------------------------------------------------------------------- /examples/drawing/src/types.ts: -------------------------------------------------------------------------------- 1 | export type OverlayGeometry = 2 | | google.maps.Marker 3 | | google.maps.Polygon 4 | | google.maps.Polyline 5 | | google.maps.Rectangle 6 | | google.maps.Circle; 7 | 8 | export interface DrawResult { 9 | type: google.maps.drawing.OverlayType; 10 | overlay: OverlayGeometry; 11 | } 12 | 13 | export interface Snapshot { 14 | radius?: number; 15 | center?: google.maps.LatLngLiteral; 16 | position?: google.maps.LatLngLiteral; 17 | path?: Array; 18 | bounds?: google.maps.LatLngBoundsLiteral; 19 | } 20 | 21 | export interface Overlay { 22 | type: google.maps.drawing.OverlayType; 23 | geometry: OverlayGeometry; 24 | snapshot: Snapshot; 25 | } 26 | 27 | export interface State { 28 | past: Array>; 29 | now: Array; 30 | future: Array>; 31 | } 32 | 33 | export enum DrawingActionKind { 34 | SET_OVERLAY = 'SET_OVERLAY', 35 | UPDATE_OVERLAYS = 'UPDATE_OVERLAYS', 36 | UNDO = 'UNDO', 37 | REDO = 'REDO' 38 | } 39 | 40 | export interface ActionWithTypeOnly { 41 | type: Exclude; 42 | } 43 | 44 | export interface SetOverlayAction { 45 | type: DrawingActionKind.SET_OVERLAY; 46 | payload: DrawResult; 47 | } 48 | 49 | export type Action = ActionWithTypeOnly | SetOverlayAction; 50 | 51 | export function isCircle( 52 | overlay: OverlayGeometry 53 | ): overlay is google.maps.Circle { 54 | return (overlay as google.maps.Circle).getCenter !== undefined; 55 | } 56 | 57 | export function isMarker( 58 | overlay: OverlayGeometry 59 | ): overlay is google.maps.Marker { 60 | return (overlay as google.maps.Marker).getPosition !== undefined; 61 | } 62 | 63 | export function isPolygon( 64 | overlay: OverlayGeometry 65 | ): overlay is google.maps.Polygon { 66 | return (overlay as google.maps.Polygon).getPath !== undefined; 67 | } 68 | 69 | export function isPolyline( 70 | overlay: OverlayGeometry 71 | ): overlay is google.maps.Polyline { 72 | return (overlay as google.maps.Polyline).getPath !== undefined; 73 | } 74 | 75 | export function isRectangle( 76 | overlay: OverlayGeometry 77 | ): overlay is google.maps.Rectangle { 78 | return (overlay as google.maps.Rectangle).getBounds !== undefined; 79 | } 80 | -------------------------------------------------------------------------------- /examples/drawing/src/use-drawing-manager.tsx: -------------------------------------------------------------------------------- 1 | import {useMap, useMapsLibrary} from '@vis.gl/react-google-maps'; 2 | import {useEffect, useState} from 'react'; 3 | 4 | export function useDrawingManager( 5 | initialValue: google.maps.drawing.DrawingManager | null = null 6 | ) { 7 | const map = useMap(); 8 | const drawing = useMapsLibrary('drawing'); 9 | 10 | const [drawingManager, setDrawingManager] = 11 | useState(initialValue); 12 | 13 | useEffect(() => { 14 | if (!map || !drawing) return; 15 | 16 | // https://developers.google.com/maps/documentation/javascript/reference/drawing 17 | const newDrawingManager = new drawing.DrawingManager({ 18 | map, 19 | drawingMode: google.maps.drawing.OverlayType.CIRCLE, 20 | drawingControl: true, 21 | drawingControlOptions: { 22 | position: google.maps.ControlPosition.TOP_CENTER, 23 | drawingModes: [ 24 | google.maps.drawing.OverlayType.MARKER, 25 | google.maps.drawing.OverlayType.CIRCLE, 26 | google.maps.drawing.OverlayType.POLYGON, 27 | google.maps.drawing.OverlayType.POLYLINE, 28 | google.maps.drawing.OverlayType.RECTANGLE 29 | ] 30 | }, 31 | markerOptions: { 32 | draggable: true 33 | }, 34 | circleOptions: { 35 | editable: true 36 | }, 37 | polygonOptions: { 38 | editable: true, 39 | draggable: true 40 | }, 41 | rectangleOptions: { 42 | editable: true, 43 | draggable: true 44 | }, 45 | polylineOptions: { 46 | editable: true, 47 | draggable: true 48 | } 49 | }); 50 | 51 | setDrawingManager(newDrawingManager); 52 | 53 | return () => { 54 | newDrawingManager.setMap(null); 55 | }; 56 | }, [drawing, map]); 57 | 58 | return drawingManager; 59 | } 60 | -------------------------------------------------------------------------------- /examples/drawing/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/extended-component-library/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Extended Component Library 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/extended-component-library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/extended-component-library": "^0.6.11", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^18.2.0", 7 | "react-dom": "^18.2.0", 8 | "vite": "^5.0.4" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | }, 15 | "devDependencies": { 16 | "@types/node": "^20.12.8", 17 | "@types/react-dom": "^18.3.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/extended-component-library/src/app.css: -------------------------------------------------------------------------------- 1 | .App { 2 | --gmpx-color-surface: #f6f5ff; 3 | --gmpx-color-on-primary: #f8e8ff; 4 | --gmpx-color-on-surface: #000; 5 | --gmpx-color-on-surface-variant: #636268; 6 | --gmpx-color-primary: #8a5cf4; 7 | --gmpx-fixed-panel-height-column-layout: 420px; 8 | --gmpx-fixed-panel-width-row-layout: 340px; 9 | 10 | height: 100%; 11 | width: 100%; 12 | background: var(--gmpx-color-surface); 13 | inset: 0; 14 | } 15 | 16 | .MainContainer { 17 | display: flex; 18 | flex-direction: column; 19 | } 20 | 21 | .SplitLayoutContainer { 22 | height: 100%; 23 | } 24 | 25 | .CollegePicker { 26 | --gmpx-color-surface: #fff; 27 | flex-grow: 1; 28 | margin: 1rem; 29 | } 30 | 31 | .CloseButton { 32 | display: block; 33 | margin: 1rem; 34 | } 35 | -------------------------------------------------------------------------------- /examples/extended-component-library/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Google Maps Platform’s Extended Component Library

7 |

8 | Google Maps Platform’s Extended Component Library is a set of Web 9 | Components that helps developers build better maps faster, and with less 10 | effort. 11 |

12 |

13 | Ultimately, these components make it easier to read, learn, customize, 14 | and maintain maps-related code. 15 |

16 | 17 | 30 |
31 | ); 32 | } 33 | 34 | export default React.memo(ControlPanel); 35 | -------------------------------------------------------------------------------- /examples/extended-component-library/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/geometry/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Map with Geometry 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/geometry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/geometry/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './circle'; 2 | export * from './polygon'; 3 | export * from './polyline'; 4 | -------------------------------------------------------------------------------- /examples/geometry/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | } 6 | -------------------------------------------------------------------------------- /examples/heatmap/README.md: -------------------------------------------------------------------------------- 1 | # Heatmap 2 | 3 | This uses the `useMapsLibrary` hook showing earthquake magnitude data in a 4 | heatmap. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/heatmap/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Heatmap 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/heatmap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@types/geojson": "^7946.0.14", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^18.2.0", 7 | "react-dom": "^18.2.0", 8 | "vite": "^5.0.4" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/heatmap/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | 6 | import ControlPanel from './control-panel'; 7 | import Heatmap from './heatmap'; 8 | import {EarthquakesGeojson, loadEarthquakeGeojson} from './earthquakes'; 9 | 10 | const API_KEY = 11 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 12 | 13 | const App = () => { 14 | const [radius, setRadius] = useState(25); 15 | const [opacity, setOpacity] = useState(0.8); 16 | 17 | const [earthquakesGeojson, setEarthquakesGeojson] = 18 | useState(); 19 | 20 | useEffect(() => { 21 | loadEarthquakeGeojson().then(data => setEarthquakesGeojson(data)); 22 | }, []); 23 | 24 | return ( 25 | 26 | 33 | 34 | {earthquakesGeojson && ( 35 | 40 | )} 41 | 42 | 48 | 49 | ); 50 | }; 51 | export default App; 52 | 53 | export function renderToDom(container: HTMLElement) { 54 | const root = createRoot(container); 55 | 56 | root.render( 57 | 58 | 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /examples/heatmap/src/earthquakes.ts: -------------------------------------------------------------------------------- 1 | import {FeatureCollection, Point} from 'geojson'; 2 | 3 | export type EarthquakeProps = { 4 | id: string; 5 | mag: number; 6 | time: number; 7 | felt: number | null; 8 | tsunami: 0 | 1; 9 | }; 10 | 11 | export type EarthquakesGeojson = FeatureCollection; 12 | 13 | export async function loadEarthquakeGeojson(): Promise { 14 | const url = new URL('../data/earthquakes.json', import.meta.url); 15 | 16 | return await fetch(url).then(res => res.json()); 17 | } 18 | -------------------------------------------------------------------------------- /examples/heatmap/src/heatmap.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useMemo} from 'react'; 2 | import {useMap, useMapsLibrary} from '@vis.gl/react-google-maps'; 3 | import {FeatureCollection, Point, GeoJsonProperties} from 'geojson'; 4 | 5 | type HeatmapProps = { 6 | geojson: FeatureCollection; 7 | radius: number; 8 | opacity: number; 9 | }; 10 | const Heatmap = ({geojson, radius, opacity}: HeatmapProps) => { 11 | const map = useMap(); 12 | const visualization = useMapsLibrary('visualization'); 13 | 14 | const heatmap = useMemo(() => { 15 | if (!visualization) return null; 16 | 17 | return new google.maps.visualization.HeatmapLayer({ 18 | radius: radius, 19 | opacity: opacity 20 | }); 21 | }, [visualization, radius, opacity]); 22 | 23 | useEffect(() => { 24 | if (!heatmap) return; 25 | 26 | heatmap.setData( 27 | geojson.features.map(point => { 28 | const [lng, lat] = point.geometry.coordinates; 29 | 30 | return { 31 | location: new google.maps.LatLng(lat, lng), 32 | weight: point.properties?.mag 33 | }; 34 | }) 35 | ); 36 | }, [heatmap, geojson, radius, opacity]); 37 | 38 | useEffect(() => { 39 | if (!heatmap) return; 40 | 41 | heatmap.setMap(map); 42 | 43 | return () => { 44 | heatmap.setMap(null); 45 | }; 46 | }, [heatmap, map]); 47 | 48 | return null; 49 | }; 50 | 51 | export default Heatmap; 52 | -------------------------------------------------------------------------------- /examples/heatmap/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/homepage-header/README.md: -------------------------------------------------------------------------------- 1 | # Homepage Header 2 | 3 | This example is used as the header-image of the [homepage for this project] 4 | [rgm-home]. 5 | 6 | ## Google Maps Platform API key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | [rgm-home]: https://visgl.github.io/react-google-maps 38 | -------------------------------------------------------------------------------- /examples/homepage-header/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/homepage-header/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/homepage-header/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/map-3d/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Photorealistic 3D Map 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/map-3d/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@turf/turf": "^7.1.0", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^18.2.0", 7 | "react-dom": "^18.2.0", 8 | "typescript": "^5.4.5", 9 | "vite": "^5.0.4" 10 | }, 11 | "scripts": { 12 | "start": "vite", 13 | "start-local": "vite --config ../vite.config.local.js", 14 | "build": "vite build" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/map-3d/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, MapMouseEvent} from '@vis.gl/react-google-maps'; 5 | import ControlPanel from './control-panel'; 6 | import {MiniMap} from './minimap'; 7 | 8 | import {Map3D, Map3DCameraProps} from './map-3d'; 9 | 10 | import './style.css'; 11 | 12 | const API_KEY = 13 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 14 | 15 | const INITIAL_VIEW_PROPS = { 16 | center: {lat: 37.72809, lng: -119.64473, altitude: 1300}, 17 | range: 5000, 18 | heading: 61, 19 | tilt: 69, 20 | roll: 0 21 | }; 22 | 23 | const Map3DExample = () => { 24 | const [viewProps, setViewProps] = useState(INITIAL_VIEW_PROPS); 25 | 26 | const handleCameraChange = useCallback((props: Map3DCameraProps) => { 27 | setViewProps(oldProps => ({...oldProps, ...props})); 28 | }, []); 29 | 30 | const handleMapClick = useCallback((ev: MapMouseEvent) => { 31 | if (!ev.detail.latLng) return; 32 | 33 | const {lat, lng} = ev.detail.latLng; 34 | setViewProps(p => ({...p, center: {lat, lng, altitude: 0}})); 35 | }, []); 36 | 37 | return ( 38 | <> 39 | 44 | 45 | 46 | 47 | ); 48 | }; 49 | 50 | const App = () => { 51 | const nonAlphaVersionLoaded = Boolean( 52 | globalThis && 53 | globalThis.google?.maps?.version && 54 | !globalThis.google?.maps?.version.endsWith('-alpha') 55 | ); 56 | 57 | if (nonAlphaVersionLoaded) { 58 | location.reload(); 59 | return; 60 | } 61 | 62 | return ( 63 | 64 | 65 | 66 | 67 | ); 68 | }; 69 | export default App; 70 | 71 | export function renderToDom(container: HTMLElement) { 72 | const root = createRoot(container); 73 | 74 | root.render( 75 | 76 | 77 | 78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /examples/map-3d/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const GMP_3D_MAPS_OVERVIEW_URL = 4 | 'https://developers.google.com/maps/documentation/javascript/3d-maps-overview'; 5 | 6 | function ControlPanel() { 7 | return ( 8 |
9 |

3D Maps

10 |

11 | This example implements a new Map3D component that renders 12 | a 3D Globe based on the new experimental{' '} 13 | 14 | Map3DElement 15 | {' '} 16 | web-component. 17 |

18 | 19 |

20 | The mini-map in the bottom right shows the position and heading of the 21 | globe camera as well as the center-point of the globe view. Clicking in 22 | the mini-map will update the center-point of the globe. 23 |

24 | 25 | 38 |
39 | ); 40 | } 41 | 42 | export default React.memo(ControlPanel); 43 | -------------------------------------------------------------------------------- /examples/map-3d/src/map-3d/index.ts: -------------------------------------------------------------------------------- 1 | export * from './map-3d'; 2 | -------------------------------------------------------------------------------- /examples/map-3d/src/map-3d/map-3d-types.ts: -------------------------------------------------------------------------------- 1 | import {DOMAttributes, RefAttributes} from 'react'; 2 | 3 | // add an overload signature for the useMapsLibrary hook, so typescript 4 | // knows what the 'maps3d' library is. 5 | declare module '@vis.gl/react-google-maps' { 6 | export function useMapsLibrary( 7 | name: 'maps3d' 8 | ): typeof google.maps.maps3d | null; 9 | } 10 | 11 | // add the custom-element to the JSX.IntrinsicElements 12 | // interface, so it can be used in jsx 13 | declare global { 14 | // eslint-disable-next-line @typescript-eslint/no-namespace 15 | namespace JSX { 16 | interface IntrinsicElements { 17 | ['gmp-map-3d']: CustomElement< 18 | google.maps.maps3d.Map3DElement, 19 | { 20 | [key in GmpMap3DAttributeNames]?: string; 21 | } 22 | >; 23 | } 24 | } 25 | } 26 | 27 | type GmpMap3DAttributeNames = keyof Omit< 28 | google.maps.maps3d.Map3DElementOptions, 29 | 'bounds' 30 | >; 31 | 32 | // a helper type for CustomElement definitions 33 | type CustomElement = Partial< 34 | TAttr & 35 | DOMAttributes & 36 | RefAttributes & { 37 | // for whatever reason, anything else doesn't work as children 38 | // of a custom element, so we allow `any` here 39 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 40 | children: any; 41 | } 42 | >; 43 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/camera-position-marker.css: -------------------------------------------------------------------------------- 1 | .camera-position-marker svg { 2 | --camera-heading: 0; 3 | 4 | transform-origin: 50% 0; 5 | translate: -50% -50%; 6 | rotate: calc(var(--camera-heading) * 1deg); 7 | } 8 | 9 | .camera-position-marker path { 10 | fill: red; 11 | stroke: black; 12 | stroke-width: 2; 13 | vector-effect: non-scaling-stroke; 14 | } 15 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/camera-position-marker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 3 | 4 | import './camera-position-marker.css'; 5 | 6 | type CameraPositionMarkerProps = { 7 | position: google.maps.LatLngAltitudeLiteral; 8 | heading: number; 9 | }; 10 | 11 | export const CameraPositionMarker = ({ 12 | position, 13 | heading 14 | }: CameraPositionMarkerProps) => ( 15 | 19 | 24 | 25 | 26 | 27 | ); 28 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/estimate-camera-position.ts: -------------------------------------------------------------------------------- 1 | import {Map3DCameraProps} from '../map-3d'; 2 | import {destination, getCoords, point} from '@turf/turf'; 3 | 4 | export function estimateCameraPosition( 5 | camera3dProps: Map3DCameraProps 6 | ): google.maps.LatLngAltitudeLiteral { 7 | const {center, heading, tilt, range} = camera3dProps; 8 | 9 | const tiltRad = (tilt / 180) * Math.PI; 10 | const height = range * Math.cos(tiltRad); 11 | const distance = range * Math.sin(tiltRad); 12 | 13 | const [lng, lat] = getCoords( 14 | destination(point([center.lng, center.lat]), distance, heading + 180, { 15 | units: 'meters' 16 | }) 17 | ); 18 | 19 | return { 20 | lat: lat as number, 21 | lng: lng as number, 22 | altitude: center.altitude + height 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/index.ts: -------------------------------------------------------------------------------- 1 | export * from './minimap'; 2 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/minimap.tsx: -------------------------------------------------------------------------------- 1 | import React, {useMemo} from 'react'; 2 | import {Map, MapMouseEvent, useMap} from '@vis.gl/react-google-maps'; 3 | 4 | import {useDebouncedEffect} from '../utility-hooks'; 5 | import {estimateCameraPosition} from './estimate-camera-position'; 6 | import {CameraPositionMarker} from './camera-position-marker'; 7 | import {ViewCenterMarker} from './view-center-marker'; 8 | 9 | import type {Map3DCameraProps} from '../map-3d'; 10 | 11 | type MiniMapProps = { 12 | camera3dProps: Map3DCameraProps; 13 | onMapClick?: (ev: MapMouseEvent) => void; 14 | }; 15 | 16 | export const MiniMap = ({camera3dProps, onMapClick}: MiniMapProps) => { 17 | const minimap = useMap('minimap'); 18 | 19 | const cameraPosition = useMemo( 20 | () => estimateCameraPosition(camera3dProps), 21 | [camera3dProps] 22 | ); 23 | 24 | useDebouncedEffect( 25 | () => { 26 | if (!minimap) return; 27 | 28 | const bounds = new google.maps.LatLngBounds(); 29 | bounds.extend(camera3dProps.center); 30 | bounds.extend(cameraPosition); 31 | 32 | const maxZoom = Math.max( 33 | 1, 34 | Math.round(24 - Math.log2(camera3dProps.range)) 35 | ); 36 | 37 | minimap.fitBounds(bounds, 120); 38 | minimap.setZoom(maxZoom); 39 | }, 40 | 200, 41 | [minimap, camera3dProps.center, camera3dProps.range, cameraPosition] 42 | ); 43 | 44 | return ( 45 | 54 | 55 | 58 | 59 | ); 60 | }; 61 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/view-center-marker.css: -------------------------------------------------------------------------------- 1 | .view-center-marker { 2 | width: 0; 3 | height: 0; 4 | } 5 | 6 | .view-center-marker .circle { 7 | width: 10px; 8 | height: 10px; 9 | background-color: blue; 10 | border: 1px solid black; 11 | border-radius: 50%; 12 | translate: -50% -50%; 13 | } 14 | -------------------------------------------------------------------------------- /examples/map-3d/src/minimap/view-center-marker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 3 | 4 | import './view-center-marker.css'; 5 | 6 | type ViewCenterMarkerProps = {position: google.maps.LatLngAltitudeLiteral}; 7 | export const ViewCenterMarker = ({position}: ViewCenterMarkerProps) => ( 8 | 9 |
10 | 11 | ); 12 | -------------------------------------------------------------------------------- /examples/map-3d/src/style.css: -------------------------------------------------------------------------------- 1 | [class$='alpha-banner'] { 2 | right: auto; 3 | left: 50%; 4 | translate: -50% 0; 5 | top: auto; 6 | bottom: 1.5rem; 7 | max-width: 600px; 8 | font-size: 0.875em; 9 | border-radius: 10px; 10 | } 11 | 12 | .minimap { 13 | position: absolute; 14 | bottom: 1.5rem; 15 | right: 1.5rem; 16 | 17 | width: 50%; 18 | max-width: 320px; 19 | aspect-ratio: 1; 20 | border-radius: 10px; 21 | overflow: hidden; 22 | box-shadow: 5px 2px 20px rgb(0, 0, 0, 0.6); 23 | } 24 | -------------------------------------------------------------------------------- /examples/map-3d/src/utility-hooks.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DependencyList, 3 | EffectCallback, 4 | Ref, 5 | useCallback, 6 | useEffect, 7 | useRef, 8 | useState 9 | } from 'react'; 10 | import isDeepEqual from 'fast-deep-equal'; 11 | 12 | export function useCallbackRef() { 13 | const [el, setEl] = useState(null); 14 | const ref = useCallback((value: T) => setEl(value), [setEl]); 15 | 16 | return [el, ref as Ref] as const; 17 | } 18 | 19 | export function useDeepCompareEffect( 20 | effect: EffectCallback, 21 | deps: DependencyList 22 | ) { 23 | const ref = useRef(undefined); 24 | 25 | if (!ref.current || !isDeepEqual(deps, ref.current)) { 26 | ref.current = deps; 27 | } 28 | 29 | // eslint-disable-next-line react-hooks/exhaustive-deps 30 | useEffect(effect, ref.current); 31 | } 32 | 33 | export function useDebouncedEffect( 34 | effect: EffectCallback, 35 | timeout: number, 36 | deps: DependencyList 37 | ) { 38 | const timerRef = useRef(0); 39 | 40 | useEffect( 41 | () => { 42 | if (timerRef.current) { 43 | clearTimeout(timerRef.current); 44 | timerRef.current = 0; 45 | } 46 | 47 | timerRef.current = setTimeout(() => effect(), timeout); 48 | return () => clearTimeout(timerRef.current); 49 | }, 50 | // eslint-disable-next-line react-hooks/exhaustive-deps 51 | [timeout, ...deps] 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /examples/map-3d/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./types", "../../types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2020", 12 | "lib": ["es2020", "dom"], 13 | "jsx": "react", 14 | "skipLibCheck": true 15 | }, 16 | "exclude": ["./dist", "./node_modules"], 17 | "include": ["src/**/*.ts", "src/**/*.tsx", "types/**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /examples/map-3d/types/global.d.ts: -------------------------------------------------------------------------------- 1 | export declare global { 2 | // const or let does not work in this case, it has to be var 3 | // eslint-disable-next-line no-var 4 | var GOOGLE_MAPS_API_KEY: string | undefined; 5 | // eslint-disable-next-line no-var, @typescript-eslint/no-explicit-any 6 | var process: any; 7 | } 8 | -------------------------------------------------------------------------------- /examples/map-3d/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/map-control/README.md: -------------------------------------------------------------------------------- 1 | # Custom Map Control Example 2 | 3 | This is an example to show how to add custom map-controls. 4 | 5 | ## Google Maps Platform API Key 6 | 7 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 8 | See [the official documentation][get-api-key] on how to create and configure your own key. 9 | 10 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 11 | file named `.env` in the example directory with the following content: 12 | 13 | ```shell title=".env" 14 | GOOGLE_MAPS_API_KEY="" 15 | ``` 16 | 17 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 18 | 19 | ## Development 20 | 21 | Go into the example-directory and run 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 27 | To start the example with the local library run 28 | 29 | ```shell 30 | npm run start-local 31 | ``` 32 | 33 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 34 | 35 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 36 | -------------------------------------------------------------------------------- /examples/map-control/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Custom Map Control 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/map-control/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/map-control/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useMemo, useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import { 5 | APIProvider, 6 | ControlPosition, 7 | Map, 8 | MapControl 9 | } from '@vis.gl/react-google-maps'; 10 | 11 | import ControlPanel from './control-panel'; 12 | import {CustomZoomControl} from './custom-zoom-control'; 13 | 14 | const API_KEY = 15 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 16 | 17 | const App = () => { 18 | const [controlPosition, setControlControlPosition] = 19 | useState(ControlPosition.LEFT_BOTTOM); 20 | 21 | const [zoom, setZoom] = useState(4); 22 | const center = useMemo(() => ({lat: 0, lng: 20}), []); 23 | 24 | return ( 25 | 26 | {/* note that we can also use a mix of controlled (zoom) an 27 | uncontrolled (center) properties here */} 28 | setZoom(ev.detail.zoom)}> 35 | 36 |
41 | Zoom: {zoom.toFixed(2)} 42 |
43 |
44 | 45 | setZoom(zoom)} 49 | /> 50 |
51 | 52 | setControlControlPosition(p)} 55 | /> 56 |
57 | ); 58 | }; 59 | 60 | export default App; 61 | 62 | export function renderToDom(container: HTMLElement) { 63 | const root = createRoot(container); 64 | 65 | root.render( 66 | 67 | 68 | 69 | ); 70 | } 71 | -------------------------------------------------------------------------------- /examples/map-control/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import {ControlPosition} from '@vis.gl/react-google-maps'; 4 | 5 | export type ControlPanelProps = { 6 | position: ControlPosition; 7 | onControlPositionChange: (position: ControlPosition) => void; 8 | }; 9 | 10 | function ControlPanel({position, onControlPositionChange}: ControlPanelProps) { 11 | const positionOptions: {key: string; value: ControlPosition}[] = []; 12 | 13 | for (const [p, v] of Object.entries(ControlPosition)) { 14 | positionOptions.push({key: p, value: v as ControlPosition}); 15 | } 16 | 17 | return ( 18 |
19 |

MapControl Example

20 |

21 | Demonstrates how to use the <MapControl> component to 22 | add custom control components to the map. 23 |

24 | 25 |
26 | 27 | 38 |
39 | 40 | 53 |
54 | ); 55 | } 56 | 57 | export default React.memo(ControlPanel); 58 | -------------------------------------------------------------------------------- /examples/map-control/src/custom-zoom-control.tsx: -------------------------------------------------------------------------------- 1 | import {ControlPosition, MapControl} from '@vis.gl/react-google-maps'; 2 | import React from 'react'; 3 | 4 | type CustomZoomControlProps = { 5 | controlPosition: ControlPosition; 6 | zoom: number; 7 | onZoomChange: (zoom: number) => void; 8 | }; 9 | 10 | export const CustomZoomControl = ({ 11 | controlPosition, 12 | zoom, 13 | onZoomChange 14 | }: CustomZoomControlProps) => { 15 | return ( 16 | 17 |
25 | 26 | onZoomChange(ev.target.valueAsNumber)} 34 | /> 35 |
36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /examples/map-control/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/marker-clustering/README.md: -------------------------------------------------------------------------------- 1 | # Marker Clustering Example 2 | 3 | ![image](https://raw.githubusercontent.com/visgl/react-google-maps/main/website/static/images/examples/marker-clustering.jpg) 4 | 5 | This is an example of how to use the [`@googlemaps/markerclusterer`](https://github.com/googlemaps/js-markerclusterer) library from Google to cluster markers on a map. 6 | 7 | ## Google Maps Platform API Key 8 | 9 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 10 | See [the official documentation][get-api-key] on how to create and configure your own key. 11 | 12 | The API key has to be provided via the environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 13 | file named `.env` in the example directory with the following content: 14 | 15 | ```shell title=".env" 16 | GOOGLE_MAPS_API_KEY="" 17 | ``` 18 | 19 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 20 | 21 | ## Development 22 | 23 | Go into the example-directory and run 24 | 25 | ```shell 26 | npm install 27 | ``` 28 | 29 | To start the example with the local library run 30 | 31 | ```shell 32 | npm run start-local 33 | ``` 34 | 35 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 36 | 37 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 38 | -------------------------------------------------------------------------------- /examples/marker-clustering/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Marker Clustering 9 | 10 | 21 | 22 | 23 |
24 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/marker-clustering/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@googlemaps/markerclusterer": "^2.5.1", 5 | "@vis.gl/react-google-maps": "latest", 6 | "react": "^18.2.0", 7 | "react-dom": "^18.2.0", 8 | "vite": "^5.0.4" 9 | }, 10 | "scripts": { 11 | "start": "vite", 12 | "start-local": "vite --config ../vite.config.local.js", 13 | "build": "vite build" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/marker-clustering/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState, useMemo} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | 4 | import {APIProvider, Map} from '@vis.gl/react-google-maps'; 5 | 6 | import {ControlPanel} from './control-panel'; 7 | import {getCategories, loadTreeDataset, Tree} from './trees'; 8 | import {ClusteredTreeMarkers} from './clustered-tree-markers'; 9 | 10 | import './style.css'; 11 | 12 | const API_KEY = 13 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 14 | 15 | /** 16 | * The App component contains the APIProvider, Map and ControlPanel and handles 17 | * data-loading and filtering. 18 | */ 19 | const App = () => { 20 | const [trees, setTrees] = useState(); 21 | const [selectedCategory, setSelectedCategory] = useState(null); 22 | 23 | // load data asynchronously 24 | useEffect(() => { 25 | loadTreeDataset().then(data => setTrees(data)); 26 | }, []); 27 | 28 | // get category information for the filter-dropdown 29 | const categories = useMemo(() => getCategories(trees), [trees]); 30 | const filteredTrees = useMemo(() => { 31 | if (!trees) return null; 32 | 33 | return trees.filter( 34 | t => !selectedCategory || t.category === selectedCategory 35 | ); 36 | }, [trees, selectedCategory]); 37 | 38 | return ( 39 | 40 | 46 | {filteredTrees && } 47 | 48 | 49 | 53 | 54 | ); 55 | }; 56 | 57 | export default App; 58 | 59 | export function renderToDom(container: HTMLElement) { 60 | const root = createRoot(container); 61 | 62 | root.render( 63 | 64 | 65 | 66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /examples/marker-clustering/src/style.css: -------------------------------------------------------------------------------- 1 | .marker-clustering-tree { 2 | font-size: 2rem; 3 | } 4 | 5 | .marker-clustering-control-panel .attribution { 6 | font-size: 0.75em; 7 | } 8 | -------------------------------------------------------------------------------- /examples/marker-clustering/src/tree-marker.tsx: -------------------------------------------------------------------------------- 1 | import {Tree} from './trees'; 2 | import type {Marker} from '@googlemaps/markerclusterer'; 3 | import React, {useCallback} from 'react'; 4 | import {AdvancedMarker} from '@vis.gl/react-google-maps'; 5 | 6 | export type TreeMarkerProps = { 7 | tree: Tree; 8 | onClick: (tree: Tree) => void; 9 | setMarkerRef: (marker: Marker | null, key: string) => void; 10 | }; 11 | 12 | /** 13 | * Wrapper Component for an AdvancedMarker for a single tree. 14 | */ 15 | export const TreeMarker = (props: TreeMarkerProps) => { 16 | const {tree, onClick, setMarkerRef} = props; 17 | 18 | const handleClick = useCallback(() => onClick(tree), [onClick, tree]); 19 | const ref = useCallback( 20 | (marker: google.maps.marker.AdvancedMarkerElement) => 21 | setMarkerRef(marker, tree.key), 22 | [setMarkerRef, tree.key] 23 | ); 24 | 25 | return ( 26 | 27 | 🌳 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /examples/marker-clustering/src/trees.ts: -------------------------------------------------------------------------------- 1 | // Data source: https://open.toronto.ca/dataset/street-tree-data/ 2 | import trees from './trees.json'; 3 | 4 | export type Tree = { 5 | key: string; 6 | name: string; 7 | category: string; 8 | position: google.maps.LatLngLiteral; 9 | }; 10 | 11 | export type CategoryData = { 12 | key: string; 13 | label: string; 14 | count: number; 15 | }; 16 | 17 | for (let i = 0; i < trees.length; i++) { 18 | (trees[i] as Tree).key = `tree-${i}`; 19 | } 20 | 21 | /** 22 | * Simulates async loading of the dataset from an external source. 23 | * (data is inlined for simplicity in our build process) 24 | */ 25 | export async function loadTreeDataset(): Promise { 26 | // simulate loading the trees from an external source 27 | return new Promise(resolve => { 28 | setTimeout(() => resolve(trees as Tree[]), 500); 29 | }); 30 | } 31 | 32 | export function getCategories(trees?: Tree[]): CategoryData[] { 33 | if (!trees) return []; 34 | 35 | const countByCategory: {[c: string]: number} = {}; 36 | for (const t of trees) { 37 | if (!countByCategory[t.category]) countByCategory[t.category] = 0; 38 | countByCategory[t.category]++; 39 | } 40 | 41 | return Object.entries(countByCategory).map(([key, value]) => { 42 | const label = key.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); 43 | return { 44 | key: key, 45 | label, 46 | count: value 47 | }; 48 | }); 49 | } 50 | 51 | export default trees as Tree[]; 52 | -------------------------------------------------------------------------------- /examples/marker-clustering/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/README.md: -------------------------------------------------------------------------------- 1 | # Markers and Infowindow Example 2 | 3 | Shows the different ways to use the ``, `` and 4 | `` components. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: 9 | 10 | 20 | 21 | 22 |
23 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.4" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Markers and InfoWindows

7 |

8 | This example shows the different ways to add markers and infowindows to 9 | the map. 10 |

11 | 24 |
25 | ); 26 | } 27 | 28 | export default React.memo(ControlPanel); 29 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/marker-with-infowindow.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import { 3 | AdvancedMarker, 4 | InfoWindow, 5 | useAdvancedMarkerRef 6 | } from '@vis.gl/react-google-maps'; 7 | 8 | export const MarkerWithInfowindow = () => { 9 | const [infowindowOpen, setInfowindowOpen] = useState(true); 10 | const [markerRef, marker] = useAdvancedMarkerRef(); 11 | 12 | return ( 13 | <> 14 | setInfowindowOpen(true)} 17 | position={{lat: 28, lng: -82}} 18 | title={'AdvancedMarker that opens an Infowindow when clicked.'} 19 | /> 20 | {infowindowOpen && ( 21 | setInfowindowOpen(false)}> 25 | This is an example for the{' '} 26 | <AdvancedMarker />{' '} 27 | combined with an Infowindow. 28 | 29 | )} 30 | 31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/src/moving-marker.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {Marker} from '@vis.gl/react-google-maps'; 3 | 4 | export const MovingMarker = () => { 5 | const [position, setPosition] = useState({ 6 | lat: 0, 7 | lng: 0 8 | }); 9 | 10 | useEffect(() => { 11 | const interval = setInterval(() => { 12 | const t = performance.now(); 13 | const lat = Math.sin(t / 2000) * 5; 14 | const lng = Math.cos(t / 3000) * 5; 15 | 16 | setPosition({lat, lng}); 17 | }, 200); 18 | 19 | return () => clearInterval(interval); 20 | }); 21 | 22 | return ; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/markers-and-infowindows/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/multiple-maps/README.md: -------------------------------------------------------------------------------- 1 | # Synchronized Maps 2 | 3 | Shows how to use controlled mode and camera-events to synchronize 4 | multiple map instances. 5 | 6 | ## Google Maps Platform API Key 7 | 8 | This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. 9 | See [the official documentation][get-api-key] on how to create and configure your own key. 10 | 11 | The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a 12 | file named `.env` in the example directory with the following content: 13 | 14 | ```shell title=".env" 15 | GOOGLE_MAPS_API_KEY="" 16 | ``` 17 | 18 | If you are on the CodeSandbox playground you can also choose to [provide the API key like this](https://codesandbox.io/docs/learn/environment/secrets) 19 | 20 | ## Development 21 | 22 | Go into the example-directory and run 23 | 24 | ```shell 25 | npm install 26 | ``` 27 | 28 | To start the example with the local library run 29 | 30 | ```shell 31 | npm run start-local 32 | ``` 33 | 34 | The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) 35 | 36 | [get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key 37 | -------------------------------------------------------------------------------- /examples/multiple-maps/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | Example: Synchronized Maps 9 | 10 | 20 | 21 | 22 |
23 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/multiple-maps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "@vis.gl/react-google-maps": "latest", 5 | "react": "^18.2.0", 6 | "react-dom": "^18.2.0", 7 | "vite": "^5.0.12" 8 | }, 9 | "scripts": { 10 | "start": "vite", 11 | "start-local": "vite --config ../vite.config.local.js", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/multiple-maps/src/app.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | import {createRoot} from 'react-dom/client'; 3 | import { 4 | APIProvider, 5 | MapCameraProps, 6 | Map, 7 | MapCameraChangedEvent 8 | } from '@vis.gl/react-google-maps'; 9 | 10 | import ControlPanel from './control-panel'; 11 | 12 | const MAP_IDS = [ 13 | 'bf51a910020fa25a', 14 | '49ae42fed52588c3', 15 | '3fec513989decfcd', 16 | '7a9e2ebecd32a903' 17 | ]; 18 | 19 | const API_KEY = 20 | globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); 21 | 22 | const INITIAL_CAMERA_STATE = { 23 | center: { 24 | lat: 40.7127753, 25 | lng: -74.0059728 26 | }, 27 | zoom: 10, 28 | heading: 0, 29 | tilt: 0 30 | }; 31 | 32 | const App = () => { 33 | const [cameraState, setCameraState] = 34 | useState(INITIAL_CAMERA_STATE); 35 | 36 | // we only want to receive cameraChanged events from the map the 37 | // user is interacting with: 38 | const [activeMap, setActiveMap] = useState(1); 39 | const handleCameraChange = useCallback((ev: MapCameraChangedEvent) => { 40 | setCameraState(ev.detail); 41 | }, []); 42 | 43 | return ( 44 | 45 |
51 | {[0, 1, 2, 3].map(i => { 52 | const isActive = activeMap === i; 53 | 54 | return ( 55 | setActiveMap(i)} 63 | {...cameraState}> 64 | ); 65 | })} 66 |
67 | 68 | 69 |
70 | ); 71 | }; 72 | 73 | export default App; 74 | 75 | export function renderToDom(container: HTMLElement) { 76 | const root = createRoot(container); 77 | 78 | root.render( 79 | 80 | 81 | 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /examples/multiple-maps/src/control-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | function ControlPanel() { 4 | return ( 5 |
6 |

Synchronized Maps

7 |

8 | Shows how to use the controlled map component to synchronize multiple 9 | map instances. 10 |

11 | 12 | 25 |
26 | ); 27 | } 28 | 29 | export default React.memo(ControlPanel); 30 | -------------------------------------------------------------------------------- /examples/multiple-maps/vite.config.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | 3 | export default defineConfig(({mode}) => { 4 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 5 | 6 | return { 7 | define: { 8 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 9 | }, 10 | resolve: { 11 | alias: { 12 | '@vis.gl/react-google-maps/examples.js': 13 | 'https://visgl.github.io/react-google-maps/scripts/examples.js' 14 | } 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /examples/vite.config.local.js: -------------------------------------------------------------------------------- 1 | import {defineConfig, loadEnv} from 'vite'; 2 | import {resolve} from 'node:path'; 3 | 4 | export default defineConfig(({mode}) => { 5 | const {GOOGLE_MAPS_API_KEY = ''} = loadEnv(mode, process.cwd(), ''); 6 | 7 | return { 8 | define: { 9 | 'process.env.GOOGLE_MAPS_API_KEY': JSON.stringify(GOOGLE_MAPS_API_KEY) 10 | }, 11 | resolve: { 12 | alias: { 13 | '@vis.gl/react-google-maps/examples.js': resolve( 14 | '../../website/static/scripts/examples.js' 15 | ), 16 | '@vis.gl/react-google-maps/examples.css': resolve( 17 | '../../examples/examples.css' 18 | ), 19 | '@vis.gl/react-google-maps': resolve('../../src/index.ts') 20 | } 21 | } 22 | }; 23 | }); 24 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [''], 3 | testEnvironment: 'jsdom', 4 | testPathIgnorePatterns: ['/node_modules/', '/dist/', '/__utils__/'], 5 | transform: { 6 | '^.+.tsx?$': ['ts-jest', {tsconfig: 'tsconfig.test.json'}] 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/install-examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | examplesRoot="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"/examples 4 | cd $examplesRoot 5 | 6 | for d in */; do 7 | echo ">>> installing example '$(basename $d)'" 8 | cd $examplesRoot/$d 9 | npm i --silent 10 | done 11 | 12 | -------------------------------------------------------------------------------- /scripts/update-examples.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #rootDir=`readlink -f "$(dirname $0)/.."` 4 | rootDir="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" 5 | 6 | for d in `find ${rootDir}/examples -type d -depth 1` ; do 7 | echo ">>> updating example '$(basename $d)'" 8 | ( 9 | cd $d 10 | npm --no-progress --no-audit --no-fund --silent update 11 | to_update=`npm outdated --json | jq -r 'to_entries[] | select(.value.wanted != .value.latest) | .key'` 12 | 13 | for pkg in $to_update ; do 14 | npm install $pkg@latest 15 | done 16 | ) 17 | 18 | done 19 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/pin.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`pin view logs an error when used outside of AdvancedMarker 1`] = ` 4 | [ 5 | "The component can only be used inside .", 6 | ] 7 | `; 8 | -------------------------------------------------------------------------------- /src/components/__tests__/__utils__/wait-for-mock-instance.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | import {mockInstances} from '@googlemaps/jest-mocks'; 3 | 4 | type Constructable = { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | new (...args: any[]): unknown; 7 | }; 8 | 9 | /** 10 | * Uses `waitFor` to detect when a specified @googlemaps/jest-mocks object has 11 | * been created and returns the last item from the list of mock objects 12 | * for that type. 13 | * @param ctor 14 | */ 15 | export async function waitForMockInstance( 16 | ctor: T 17 | ): Promise> { 18 | await waitFor(() => expect(mockInstances.get(ctor)).not.toHaveLength(0)); 19 | 20 | return mockInstances.get(ctor).at(-1)!; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/__tests__/__utils__/wait-for-spy.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | 3 | /** 4 | * Waits for a specified spy/mock-function to be called and returns the 5 | * arguments passed to the last call. 6 | * @param spy 7 | */ 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | export async function waitForSpy any>( 10 | spy: 11 | | jest.Mock, Parameters> 12 | | jest.SpyInstance, Parameters> 13 | ): Promise | undefined> { 14 | await waitFor(() => expect(spy).toHaveBeenCalled()); 15 | 16 | return spy.mock.lastCall; 17 | } 18 | -------------------------------------------------------------------------------- /src/components/__tests__/map-control.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | 3 | import React, {ReactElement} from 'react'; 4 | import {initialize} from '@googlemaps/jest-mocks'; 5 | import {cleanup, render} from '@testing-library/react'; 6 | 7 | import {APIProvider} from '../api-provider'; 8 | import {Map} from '../map'; 9 | import {ControlPosition, MapControl} from '../map-control'; 10 | import {waitForMockInstance} from './__utils__/wait-for-mock-instance'; 11 | 12 | jest.mock('../../libraries/google-maps-api-loader'); 13 | 14 | let wrapper: ({children}: {children: React.ReactNode}) => ReactElement | null; 15 | 16 | beforeEach(() => { 17 | initialize(); 18 | 19 | wrapper = ({children}: {children: React.ReactNode}) => ( 20 | 21 | 22 | {children} 23 | 24 | 25 | ); 26 | }); 27 | 28 | afterEach(() => { 29 | cleanup(); 30 | jest.restoreAllMocks(); 31 | }); 32 | 33 | test('control is added to the map', async () => { 34 | render( 35 | 36 | 37 | , 38 | {wrapper} 39 | ); 40 | 41 | const map = await waitForMockInstance(google.maps.Map); 42 | const controlsArray = map.controls[ControlPosition.BOTTOM_CENTER]; 43 | 44 | expect(controlsArray.push).toHaveBeenCalled(); 45 | 46 | const [controlEl] = (controlsArray.push as jest.Mock).mock.calls[0]; 47 | expect(controlEl).toHaveTextContent('control button'); 48 | }); 49 | -------------------------------------------------------------------------------- /src/components/map-control.tsx: -------------------------------------------------------------------------------- 1 | import {useEffect, useMemo} from 'react'; 2 | import {createPortal} from 'react-dom'; 3 | import {useMap} from '../hooks/use-map'; 4 | 5 | import type {PropsWithChildren} from 'react'; 6 | 7 | type MapControlProps = PropsWithChildren<{ 8 | position: ControlPosition; 9 | }>; 10 | 11 | /** 12 | * Copy of the `google.maps.ControlPosition` constants. 13 | * They have to be duplicated here since we can't wait for the maps API to load to be able to use them. 14 | */ 15 | export const ControlPosition = { 16 | TOP_LEFT: 1, 17 | TOP_CENTER: 2, 18 | TOP: 2, 19 | TOP_RIGHT: 3, 20 | LEFT_CENTER: 4, 21 | LEFT_TOP: 5, 22 | LEFT: 5, 23 | LEFT_BOTTOM: 6, 24 | RIGHT_TOP: 7, 25 | RIGHT: 7, 26 | RIGHT_CENTER: 8, 27 | RIGHT_BOTTOM: 9, 28 | BOTTOM_LEFT: 10, 29 | BOTTOM_CENTER: 11, 30 | BOTTOM: 11, 31 | BOTTOM_RIGHT: 12, 32 | CENTER: 13, 33 | BLOCK_START_INLINE_START: 14, 34 | BLOCK_START_INLINE_CENTER: 15, 35 | BLOCK_START_INLINE_END: 16, 36 | INLINE_START_BLOCK_CENTER: 17, 37 | INLINE_START_BLOCK_START: 18, 38 | INLINE_START_BLOCK_END: 19, 39 | INLINE_END_BLOCK_START: 20, 40 | INLINE_END_BLOCK_CENTER: 21, 41 | INLINE_END_BLOCK_END: 22, 42 | BLOCK_END_INLINE_START: 23, 43 | BLOCK_END_INLINE_CENTER: 24, 44 | BLOCK_END_INLINE_END: 25 45 | } as const; 46 | export type ControlPosition = 47 | (typeof ControlPosition)[keyof typeof ControlPosition]; 48 | 49 | export const MapControl = ({children, position}: MapControlProps) => { 50 | const controlContainer = useMemo(() => document.createElement('div'), []); 51 | const map = useMap(); 52 | 53 | useEffect(() => { 54 | if (!map) return; 55 | 56 | const controls = map.controls[position]; 57 | 58 | controls.push(controlContainer); 59 | 60 | return () => { 61 | const controlsArray = controls.getArray(); 62 | // controlsArray could be undefined if the map is in an undefined state (e.g. invalid API-key, see #276 63 | if (!controlsArray) return; 64 | 65 | const index = controlsArray.indexOf(controlContainer); 66 | controls.removeAt(index); 67 | }; 68 | }, [controlContainer, map, position]); 69 | 70 | return createPortal(children, controlContainer); 71 | }; 72 | -------------------------------------------------------------------------------- /src/components/map/auth-failure-message.tsx: -------------------------------------------------------------------------------- 1 | import React, {CSSProperties} from 'react'; 2 | 3 | export const AuthFailureMessage = () => { 4 | const style: CSSProperties = { 5 | position: 'absolute', 6 | top: 0, 7 | left: 0, 8 | bottom: 0, 9 | right: 0, 10 | zIndex: 999, 11 | display: 'flex', 12 | flexFlow: 'column nowrap', 13 | textAlign: 'center', 14 | justifyContent: 'center', 15 | fontSize: '.8rem', 16 | color: 'rgba(0,0,0,0.6)', 17 | background: '#dddddd', 18 | padding: '1rem 1.5rem' 19 | }; 20 | 21 | return ( 22 |
23 |

Error: AuthFailure

24 |

25 | A problem with your API key prevents the map from rendering correctly. 26 | Please make sure the value of the APIProvider.apiKey prop 27 | is correct. Check the error-message in the console for further details. 28 |

29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/map/use-deckgl-camera-update.ts: -------------------------------------------------------------------------------- 1 | import {useLayoutEffect} from 'react'; 2 | 3 | export type DeckGlCompatProps = { 4 | /** 5 | * Viewport from deck.gl 6 | */ 7 | viewport?: unknown; 8 | /** 9 | * View state from deck.gl 10 | */ 11 | viewState?: Record; 12 | /** 13 | * Initial View State from deck.gl 14 | */ 15 | initialViewState?: Record; 16 | }; 17 | 18 | /** 19 | * Internal hook that updates the camera when deck.gl viewState changes. 20 | * @internal 21 | */ 22 | export function useDeckGLCameraUpdate( 23 | map: google.maps.Map | null, 24 | props: DeckGlCompatProps 25 | ) { 26 | const {viewport, viewState} = props; 27 | const isDeckGlControlled = !!viewport; 28 | 29 | useLayoutEffect(() => { 30 | if (!map || !viewState) return; 31 | 32 | const { 33 | latitude, 34 | longitude, 35 | bearing: heading, 36 | pitch: tilt, 37 | zoom 38 | } = viewState as Record; 39 | 40 | map.moveCamera({ 41 | center: {lat: latitude, lng: longitude}, 42 | heading, 43 | tilt, 44 | zoom: zoom + 1 45 | }); 46 | }, [map, viewState]); 47 | 48 | return isDeckGlControlled; 49 | } 50 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__snapshots__/use-map.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`it should log an error when used outside the APIProvider 1`] = ` 4 | [ 5 | [ 6 | "useMap(): failed to retrieve APIProviderContext. Make sure that the component exists and that the component you are calling \`useMap()\` from is a sibling of the .", 7 | ], 8 | ] 9 | `; 10 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__utils__/wait-for-mock-instance.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | import {mockInstances} from '@googlemaps/jest-mocks'; 3 | 4 | type Constructable = { 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | new (...args: any[]): unknown; 7 | }; 8 | 9 | /** 10 | * Uses `waitFor` to detect when a specified @googlemaps/jest-mocks object has 11 | * been created and returns the last item from the list of mock objects 12 | * for that type. 13 | * @param ctor 14 | */ 15 | export async function waitForMockInstance( 16 | ctor: T 17 | ): Promise> { 18 | await waitFor(() => expect(mockInstances.get(ctor)).not.toHaveLength(0)); 19 | 20 | return mockInstances.get(ctor).at(-1)!; 21 | } 22 | -------------------------------------------------------------------------------- /src/hooks/__tests__/__utils__/wait-for-spy.ts: -------------------------------------------------------------------------------- 1 | import {waitFor} from '@testing-library/react'; 2 | 3 | /** 4 | * Waits for a specified spy/mock-function to be called and returns the 5 | * arguments passed to the last call. 6 | * @param spy 7 | */ 8 | export async function waitForSpy( 9 | spy: T 10 | ): Promise> { 11 | await waitFor(() => expect(spy).toHaveBeenCalled()); 12 | 13 | return spy.mock.lastCall; 14 | } 15 | -------------------------------------------------------------------------------- /src/hooks/use-api-is-loaded.ts: -------------------------------------------------------------------------------- 1 | import {useApiLoadingStatus} from './use-api-loading-status'; 2 | import {APILoadingStatus} from '../libraries/api-loading-status'; 3 | /** 4 | * Hook to check if the Maps JavaScript API is loaded 5 | */ 6 | export function useApiIsLoaded(): boolean { 7 | const status = useApiLoadingStatus(); 8 | 9 | return status === APILoadingStatus.LOADED; 10 | } 11 | -------------------------------------------------------------------------------- /src/hooks/use-api-loading-status.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | import {APIProviderContext} from '../components/api-provider'; 3 | import {APILoadingStatus} from '../libraries/api-loading-status'; 4 | 5 | export function useApiLoadingStatus(): APILoadingStatus { 6 | return useContext(APIProviderContext)?.status || APILoadingStatus.NOT_LOADED; 7 | } 8 | -------------------------------------------------------------------------------- /src/hooks/use-dom-event-listener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import {useEffect} from 'react'; 3 | 4 | /** 5 | * Internally used to bind events to DOM nodes. 6 | * @internal 7 | */ 8 | export function useDomEventListener void>( 9 | target?: Node | null, 10 | name?: string, 11 | callback?: T | null 12 | ) { 13 | useEffect(() => { 14 | if (!target || !name || !callback) return; 15 | 16 | target.addEventListener(name, callback); 17 | 18 | return () => target.removeEventListener(name, callback); 19 | }, [target, name, callback]); 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/use-map.ts: -------------------------------------------------------------------------------- 1 | import {useContext} from 'react'; 2 | 3 | import {APIProviderContext} from '../components/api-provider'; 4 | import {GoogleMapsContext} from '../components/map'; 5 | import {logErrorOnce} from '../libraries/errors'; 6 | 7 | /** 8 | * Retrieves a map-instance from the context. This is either an instance 9 | * identified by id or the parent map instance if no id is specified. 10 | * Returns null if neither can be found. 11 | */ 12 | export const useMap = (id: string | null = null): google.maps.Map | null => { 13 | const ctx = useContext(APIProviderContext); 14 | const {map} = useContext(GoogleMapsContext) || {}; 15 | 16 | if (ctx === null) { 17 | logErrorOnce( 18 | 'useMap(): failed to retrieve APIProviderContext. ' + 19 | 'Make sure that the component exists and that the ' + 20 | 'component you are calling `useMap()` from is a sibling of the ' + 21 | '.' 22 | ); 23 | 24 | return null; 25 | } 26 | 27 | const {mapInstances} = ctx; 28 | 29 | // if an id is specified, the corresponding map or null is returned 30 | if (id !== null) return mapInstances[id] || null; 31 | 32 | // otherwise, return the closest ancestor 33 | if (map) return map; 34 | 35 | // finally, return the default map instance 36 | return mapInstances['default'] || null; 37 | }; 38 | -------------------------------------------------------------------------------- /src/hooks/use-maps-event-listener.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import {useEffect} from 'react'; 3 | 4 | /** 5 | * Internally used to bind events to Maps JavaScript API objects. 6 | * @internal 7 | */ 8 | export function useMapsEventListener void>( 9 | target?: object | null, 10 | name?: string, 11 | callback?: T | null 12 | ) { 13 | useEffect(() => { 14 | if (!target || !name || !callback) return; 15 | 16 | const listener = google.maps.event.addListener(target, name, callback); 17 | 18 | return () => listener.remove(); 19 | }, [target, name, callback]); 20 | } 21 | -------------------------------------------------------------------------------- /src/hooks/use-maps-library.ts: -------------------------------------------------------------------------------- 1 | import {useContext, useEffect} from 'react'; 2 | 3 | import {APIProviderContext} from '../components/api-provider'; 4 | import {useApiIsLoaded} from './use-api-is-loaded'; 5 | 6 | interface ApiLibraries { 7 | core: google.maps.CoreLibrary; 8 | maps: google.maps.MapsLibrary; 9 | places: google.maps.PlacesLibrary; 10 | geocoding: google.maps.GeocodingLibrary; 11 | routes: google.maps.RoutesLibrary; 12 | marker: google.maps.MarkerLibrary; 13 | geometry: google.maps.GeometryLibrary; 14 | elevation: google.maps.ElevationLibrary; 15 | streetView: google.maps.StreetViewLibrary; 16 | journeySharing: google.maps.JourneySharingLibrary; 17 | drawing: google.maps.DrawingLibrary; 18 | visualization: google.maps.VisualizationLibrary; 19 | } 20 | 21 | export function useMapsLibrary< 22 | K extends keyof ApiLibraries, 23 | V extends ApiLibraries[K] 24 | >(name: K): V | null; 25 | 26 | export function useMapsLibrary(name: string) { 27 | const apiIsLoaded = useApiIsLoaded(); 28 | const ctx = useContext(APIProviderContext); 29 | 30 | useEffect(() => { 31 | if (!apiIsLoaded || !ctx) return; 32 | 33 | // Trigger loading the libraries via our proxy-method. 34 | // The returned promise is ignored, since importLibrary will update loadedLibraries 35 | // list in the context, triggering a re-render. 36 | void ctx.importLibrary(name); 37 | }, [apiIsLoaded, ctx, name]); 38 | 39 | return ctx?.loadedLibraries[name] || null; 40 | } 41 | -------------------------------------------------------------------------------- /src/hooks/use-prop-binding.ts: -------------------------------------------------------------------------------- 1 | import {useEffect} from 'react'; 2 | 3 | /** 4 | * Internally used to copy values from props into API-Objects 5 | * whenever they change. 6 | * 7 | * @example 8 | * usePropBinding(marker, 'position', position); 9 | * 10 | * @internal 11 | */ 12 | export function usePropBinding( 13 | object: T | null, 14 | prop: K, 15 | value: T[K] 16 | ) { 17 | useEffect(() => { 18 | if (!object) return; 19 | 20 | object[prop] = value; 21 | }, [object, prop, value]); 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | export * from './components/advanced-marker'; 4 | export * from './components/api-provider'; 5 | export * from './components/info-window'; 6 | export * from './components/map'; 7 | export * from './components/map-control'; 8 | export * from './components/marker'; 9 | export * from './components/pin'; 10 | export * from './hooks/use-api-loading-status'; 11 | export * from './hooks/use-api-is-loaded'; 12 | export * from './hooks/use-maps-library'; 13 | export * from './hooks/use-map'; 14 | export * from './libraries/lat-lng-utils'; 15 | export * from './libraries/api-loading-status'; 16 | export {limitTiltRange} from './libraries/limit-tilt-range'; 17 | -------------------------------------------------------------------------------- /src/libraries/__mocks__/google-maps-api-loader.ts: -------------------------------------------------------------------------------- 1 | import type {GoogleMapsApiLoader as ActualLoader} from '../google-maps-api-loader'; 2 | 3 | // FIXME: this should no longer be needed with the next version of @googlemaps/jest-mocks 4 | import {importLibraryMock} from './lib/import-library-mock'; 5 | import {APILoadingStatus} from '../api-loading-status'; 6 | 7 | export class GoogleMapsApiLoader { 8 | static loadingStatus: APILoadingStatus = APILoadingStatus.LOADED; 9 | static load: typeof ActualLoader.load = jest.fn( 10 | (_, onLoadingStatusChange) => { 11 | google.maps.importLibrary = importLibraryMock; 12 | onLoadingStatusChange(APILoadingStatus.LOADED); 13 | return Promise.resolve(); 14 | } 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /src/libraries/__tests__/__snapshots__/google-maps-api-loader.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`GoogleMapsApiLoader logs a warning when called multiple times with different parameters 1`] = ` 4 | [ 5 | [ 6 | "[google-maps-api-loader] The maps API has already been loaded with different parameters and will not be loaded again. Refresh the page for new values to have effect.", 7 | ], 8 | ] 9 | `; 10 | -------------------------------------------------------------------------------- /src/libraries/api-loading-status.ts: -------------------------------------------------------------------------------- 1 | export const APILoadingStatus = { 2 | NOT_LOADED: 'NOT_LOADED', 3 | LOADING: 'LOADING', 4 | LOADED: 'LOADED', 5 | FAILED: 'FAILED', 6 | AUTH_FAILURE: 'AUTH_FAILURE' 7 | }; 8 | export type APILoadingStatus = 9 | (typeof APILoadingStatus)[keyof typeof APILoadingStatus]; 10 | -------------------------------------------------------------------------------- /src/libraries/assert-not-null.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A typescript assertion function used in cases where typescript has to be 3 | * convinced that the object in question can not be null. 4 | * 5 | * @param value 6 | * @param message 7 | */ 8 | export function assertNotNull( 9 | value: TValue, 10 | message = 'assertion failed' 11 | ): asserts value is NonNullable { 12 | if (value === null || value === undefined) { 13 | throw Error(message); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/libraries/errors.ts: -------------------------------------------------------------------------------- 1 | const shownMessages = new Set(); 2 | 3 | export function logErrorOnce(...args: Parameters) { 4 | const key = JSON.stringify(args); 5 | 6 | if (!shownMessages.has(key)) { 7 | shownMessages.add(key); 8 | 9 | console.error(...args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/libraries/lat-lng-utils.ts: -------------------------------------------------------------------------------- 1 | export function isLatLngLiteral( 2 | obj: unknown 3 | ): obj is google.maps.LatLngLiteral { 4 | if (!obj || typeof obj !== 'object') return false; 5 | if (!('lat' in obj && 'lng' in obj)) return false; 6 | 7 | return Number.isFinite(obj.lat) && Number.isFinite(obj.lng); 8 | } 9 | 10 | export function latLngEquals( 11 | a: google.maps.LatLngLiteral | google.maps.LatLng | undefined | null, 12 | b: google.maps.LatLngLiteral | google.maps.LatLng | undefined | null 13 | ): boolean { 14 | if (!a || !b) return false; 15 | const A = toLatLngLiteral(a); 16 | const B = toLatLngLiteral(b); 17 | if (A.lat !== B.lat || A.lng !== B.lng) return false; 18 | return true; 19 | } 20 | 21 | export function toLatLngLiteral( 22 | obj: google.maps.LatLngLiteral | google.maps.LatLng 23 | ): google.maps.LatLngLiteral { 24 | if (isLatLngLiteral(obj)) return obj; 25 | 26 | return obj.toJSON(); 27 | } 28 | -------------------------------------------------------------------------------- /src/libraries/limit-tilt-range.ts: -------------------------------------------------------------------------------- 1 | const mapLinear = (x: number, a1: number, a2: number, b1: number, b2: number) => 2 | b1 + ((x - a1) * (b2 - b1)) / (a2 - a1); 3 | 4 | const getMapMaxTilt = (zoom: number) => { 5 | if (zoom <= 10) { 6 | return 30; 7 | } 8 | if (zoom >= 15.5) { 9 | return 67.5; 10 | } 11 | 12 | // range [10...14] 13 | if (zoom <= 14) { 14 | return mapLinear(zoom, 10, 14, 30, 45); 15 | } 16 | 17 | // range [14...15.5] 18 | return mapLinear(zoom, 14, 15.5, 45, 67.5); 19 | }; 20 | 21 | /** 22 | * Function to limit the tilt range of the Google map when updating the view state 23 | */ 24 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 25 | export const limitTiltRange = ({viewState}: any) => { 26 | const pitch = viewState.pitch; 27 | const gmZoom = viewState.zoom + 1; 28 | const maxTilt = getMapMaxTilt(gmZoom); 29 | 30 | return {...viewState, fovy: 25, pitch: Math.min(maxTilt, pitch)}; 31 | }; 32 | -------------------------------------------------------------------------------- /src/libraries/use-callback-ref.tsx: -------------------------------------------------------------------------------- 1 | import {Ref, useCallback, useState} from 'react'; 2 | 3 | export function useCallbackRef() { 4 | const [el, setEl] = useState(null); 5 | const ref = useCallback((value: T) => setEl(value), [setEl]); 6 | 7 | return [el, ref as Ref] as const; 8 | } 9 | -------------------------------------------------------------------------------- /src/libraries/use-deep-compare-effect.tsx: -------------------------------------------------------------------------------- 1 | import {DependencyList, EffectCallback, useEffect, useRef} from 'react'; 2 | import isDeepEqual from 'fast-deep-equal'; 3 | 4 | export function useDeepCompareEffect( 5 | effect: EffectCallback, 6 | deps: DependencyList 7 | ) { 8 | const ref = useRef(undefined); 9 | 10 | if (!ref.current || !isDeepEqual(deps, ref.current)) { 11 | ref.current = deps; 12 | } 13 | 14 | // eslint-disable-next-line react-hooks/exhaustive-deps 15 | useEffect(effect, ref.current); 16 | } 17 | -------------------------------------------------------------------------------- /src/libraries/use-force-update.ts: -------------------------------------------------------------------------------- 1 | import {useReducer} from 'react'; 2 | 3 | export function useForceUpdate(): () => void { 4 | const [, forceUpdate] = useReducer(x => x + 1, 0); 5 | 6 | return forceUpdate; 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["./src/**/__*__", "./examples"], 4 | "compilerOptions": { 5 | "noEmit": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "typeRoots": ["./src/types", "./node_modules/@types"], 4 | "strict": true, 5 | "sourceMap": true, 6 | "noEmit": true, 7 | "noImplicitAny": true, 8 | "module": "ESNext", 9 | "moduleResolution": "bundler", 10 | "esModuleInterop": true, 11 | "target": "ES2015", 12 | "lib": ["es2015", "dom"], 13 | "jsx": "react", 14 | 15 | "skipLibCheck": true, 16 | "paths": { 17 | "@vis.gl/react-google-maps": ["./src"] 18 | } 19 | }, 20 | "include": ["src/**/*"], 21 | "exclude": ["./dist", "./node_modules"] 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*"], 4 | "exclude": ["./examples/**/*"], 5 | "compilerOptions": { 6 | "noEmit": false 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /website/.eslintignore: -------------------------------------------------------------------------------- 1 | ../dist 2 | ../examples 3 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | /.docusaurus 4 | /build 5 | -------------------------------------------------------------------------------- /website/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | const util = require('util'); 13 | module.exports = function ( 14 | context, 15 | opts = { 16 | resolve: {modules: [], alias: {}}, 17 | debug: false, 18 | module: {}, 19 | plugins: [] 20 | } 21 | ) { 22 | return { 23 | name: 'ocular-docusaurus-plugin', 24 | configureWebpack(_config, isServer, utils) { 25 | const {resolve, debug, module, plugins} = opts; 26 | 27 | // Custom merging 28 | if (resolve) { 29 | if (resolve.modules) { 30 | _config.resolve.modules = resolve.modules; 31 | } 32 | Object.assign(_config.resolve.alias, resolve.alias); 33 | } 34 | 35 | // Symlink docs crash otherwise, see https://github.com/facebook/docusaurus/issues/6257 36 | _config.resolve.symlinks = false; 37 | 38 | if (isServer) { 39 | return { 40 | devtool: debug ? 'eval' : false, 41 | module, 42 | plugins, 43 | node: {__dirname: true} 44 | }; 45 | } 46 | return {module, plugins}; 47 | } 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-google-maps-website", 3 | "scripts": { 4 | "docusaurus": "docusaurus", 5 | "start": "docusaurus start", 6 | "build": "docusaurus clear && docusaurus build", 7 | "swizzle": "docusaurus swizzle", 8 | "clear": "docusaurus clear", 9 | "serve": "docusaurus serve", 10 | "postinstall": "bash ../scripts/install-examples.sh" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "styled-components": "^6.1.12" 16 | }, 17 | "devDependencies": { 18 | "@docusaurus/core": "^3.5.2", 19 | "@docusaurus/plugin-content-docs": "^3.5.2", 20 | "@docusaurus/preset-classic": "^3.5.2", 21 | "@easyops-cn/docusaurus-search-local": "^0.45.0", 22 | "babel-plugin-styled-components": "^2.0.0", 23 | "babel-plugin-version-inline": "^1.0.0", 24 | "glob": "^11.0.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /website/src/components/common.jsx: -------------------------------------------------------------------------------- 1 | export const isMobile = props => `@media screen and (max-width: 480px)`; 2 | -------------------------------------------------------------------------------- /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 | > header { 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 | if (route.path === indexPath) { 23 | return ( 24 |
25 | 26 |
27 | ); 28 | } 29 | 30 | return ( 31 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /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/plugin-content-docs/client'; 5 | import useBaseUrl from '@docusaurus/useBaseUrl'; 6 | 7 | import { 8 | MainExamples, 9 | ExamplesGroup, 10 | ExampleCard, 11 | ExampleHeader, 12 | ExampleTitle 13 | } from './styled'; 14 | 15 | function renderItem(item, getThumbnail) { 16 | const imageUrl = useBaseUrl(getThumbnail(item)); 17 | const {label, href} = item; 18 | 19 | return ( 20 | 21 | {label} 22 | 23 | {label} 24 | 25 | 26 | ); 27 | } 28 | 29 | function renderCategory({label, items}, getThumbnail) { 30 | return [ 31 | {label}, 32 | 33 | {items.map(item => renderItem(item, getThumbnail))} 34 | 35 | ]; 36 | } 37 | 38 | export default function ExamplesIndex({getThumbnail}) { 39 | const sidebar = useDocsSidebar(); 40 | 41 | return ( 42 | 43 | {sidebar.items.map(item => { 44 | if (item.type === 'category') { 45 | return renderCategory(item, getThumbnail); 46 | } 47 | return null; 48 | })} 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /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 | display: inline-block; 9 | padding: 20px 20px 4px 0; 10 | ${isMobile} { 11 | margin: 0; 12 | } 13 | `; 14 | 15 | export const MainExamples = styled.main` 16 | padding: 16px 0; 17 | `; 18 | 19 | export const ExamplesGroup = styled.main` 20 | display: flex; 21 | flex-wrap: wrap; 22 | padding: 16px; 23 | ${isMobile} { 24 | padding-inline: 0; 25 | } 26 | `; 27 | 28 | export const ExampleCard = styled.a` 29 | cursor: pointer; 30 | text-decoration: none; 31 | width: 240px; 32 | max-width: 50%; 33 | line-height: 0; 34 | outline: none; 35 | padding: 4px; 36 | position: relative; 37 | 38 | img { 39 | transition-property: filter; 40 | transition-duration: var(--ifm-transition-slow); 41 | transition-timing-function: var(--ifm-transition-timing-default); 42 | } 43 | &:hover { 44 | box-shadow: var(--ifm-global-shadow-md); 45 | } 46 | &:hover img { 47 | filter: contrast(0.2); 48 | } 49 | 50 | ${isMobile} { 51 | width: 50%; 52 | } 53 | `; 54 | 55 | export const ExampleTitle = styled.div` 56 | position: absolute; 57 | display: flex; 58 | justify-content: center; 59 | flex-direction: column; 60 | color: var(--ifm-color-white); 61 | font-size: 1.5em; 62 | text-align: center; 63 | line-height: initial; 64 | width: 90%; 65 | height: 90%; 66 | top: 5%; 67 | left: 5%; 68 | border: solid 1px var(--ifm-color-white); 69 | opacity: 0; 70 | transition-property: opacity; 71 | transition-duration: var(--ifm-transition-slow); 72 | transition-timing-function: var(--ifm-transition-timing-default); 73 | &:hover { 74 | opacity: 1; 75 | } 76 | `; 77 | -------------------------------------------------------------------------------- /website/src/components/home/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 3 | import { 4 | Banner, 5 | BannerContainer, 6 | HeroExampleContainer, 7 | ProjectName, 8 | GetStartedLink 9 | } from './styled'; 10 | 11 | export default function renderPage({HeroExample, children}) { 12 | const {siteConfig} = useDocusaurusContext(); 13 | 14 | // Note: The Layout "wrapper" component adds header and footer etc 15 | return ( 16 | <> 17 | 18 | 19 | {HeroExample && } 20 | 21 | 22 | {siteConfig.title} 23 |

{siteConfig.tagline}

24 | GET STARTED 25 |
26 |
27 | {children} 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /website/src/components/home/styled.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import {isMobile} from '../common'; 3 | 4 | export const Banner = styled.section` 5 | position: relative; 6 | height: 30rem; 7 | background: #8ad8ec; 8 | color: #2b3848; 9 | z-index: 0; 10 | ${isMobile} { 11 | height: 80vh; 12 | } 13 | `; 14 | 15 | export const Container = styled.div` 16 | position: relative; 17 | padding: 2rem; 18 | max-width: 80rem; 19 | width: 100%; 20 | height: 100%; 21 | margin: 0; 22 | `; 23 | 24 | export const BannerContainer = styled(Container)` 25 | position: absolute; 26 | bottom: 0; 27 | height: auto; 28 | padding-left: 4rem; 29 | z-index: 0; 30 | pointer-events: none; 31 | max-width: 750px; 32 | 33 | h1, 34 | p { 35 | } 36 | `; 37 | 38 | export const HeroExampleContainer = styled.div` 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | right: 0; 43 | bottom: 0; 44 | z-index: -1; 45 | pointer-events: none; 46 | `; 47 | 48 | export const Section = styled.section` 49 | &:nth-child(2n + 1) { 50 | background: var(--ifm-color-gray-300); 51 | } 52 | `; 53 | 54 | export const ProjectName = styled.h1` 55 | font-size: 5em; 56 | line-height: 1; 57 | text-transform: uppercase; 58 | letter-spacing: 4px; 59 | font-weight: 700; 60 | margin: 0; 61 | margin-bottom: 16px; 62 | 63 | @media (max-width: 760px) { 64 | font-size: 3em; 65 | max-width: 420px; 66 | } 67 | `; 68 | 69 | export const GetStartedLink = styled.a` 70 | pointer-events: all; 71 | font-size: 12px; 72 | line-height: 44px; 73 | letter-spacing: 2px; 74 | font-weight: bold; 75 | margin: 24px 0; 76 | padding: 0 4rem; 77 | display: inline-block; 78 | text-decoration: none; 79 | transition: 80 | background-color 250ms ease-in, 81 | color 250ms ease-in; 82 | background: white; 83 | color: #2b3848; 84 | 85 | &:visited { 86 | color: #2b3848; 87 | } 88 | &:active { 89 | color: var(--ifm-color-white); 90 | } 91 | &:hover { 92 | color: var(--ifm-color-white); 93 | background-color: #2b3848; 94 | } 95 | `; 96 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /website/src/examples-sidebar.js: -------------------------------------------------------------------------------- 1 | const sidebars = { 2 | examplesSidebar: [ 3 | { 4 | type: 'doc', 5 | label: 'Overview', 6 | id: 'index' 7 | }, 8 | { 9 | type: 'category', 10 | label: 'Examples', 11 | collapsed: false, 12 | items: [ 13 | 'basic-map', 14 | 'change-map-styles', 15 | 'markers-and-infowindows', 16 | 'advanced-marker-interaction', 17 | 'map-control', 18 | 'multiple-maps', 19 | 'marker-clustering', 20 | 'custom-marker-clustering', 21 | 'geometry', 22 | 'heatmap', 23 | 'drawing', 24 | 'autocomplete', 25 | 'directions', 26 | 'deckgl-overlay', 27 | 'map-3d', 28 | 'extended-component-library' 29 | ] 30 | } 31 | ] 32 | }; 33 | 34 | module.exports = sidebars; 35 | -------------------------------------------------------------------------------- /website/src/examples/advanced-marker-interaction.mdx: -------------------------------------------------------------------------------- 1 | # Advanced Marker interaction 2 | 3 | import App from 'website-examples/advanced-marker-interaction/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/autocomplete.mdx: -------------------------------------------------------------------------------- 1 | # Autocomplete 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | import App from 'website-examples/autocomplete/src/app'; 5 | 6 | {() => } 7 | -------------------------------------------------------------------------------- /website/src/examples/basic-map.mdx: -------------------------------------------------------------------------------- 1 | # Basic Map 2 | 3 | import App from 'website-examples/basic-map/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/change-map-styles.mdx: -------------------------------------------------------------------------------- 1 | # Change Map Styles 2 | 3 | import App from 'website-examples/change-map-styles/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/custom-marker-clustering.mdx: -------------------------------------------------------------------------------- 1 | # Custom Marker Clustering 2 | 3 | import App from 'website-examples/custom-marker-clustering/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/deckgl-overlay.mdx: -------------------------------------------------------------------------------- 1 | # Deck.gl Overlay 2 | 3 | import App from 'website-examples/deckgl-overlay/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/directions.mdx: -------------------------------------------------------------------------------- 1 | # Directions 2 | 3 | import App from 'website-examples/directions/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/drawing.mdx: -------------------------------------------------------------------------------- 1 | # Drawing 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | import App from 'website-examples/drawing/src/app'; 5 | 6 | {() => } 7 | -------------------------------------------------------------------------------- /website/src/examples/extended-component-library.mdx: -------------------------------------------------------------------------------- 1 | # Extended Component Library 2 | 3 | import BrowserOnly from '@docusaurus/BrowserOnly'; 4 | 5 | 6 | {() => { 7 | const App = require('website-examples/extended-component-library/src/app').default; 8 | 9 | return ; 10 | }} 11 | 12 | -------------------------------------------------------------------------------- /website/src/examples/geometry.mdx: -------------------------------------------------------------------------------- 1 | # Geometry 2 | 3 | import App from 'website-examples/geometry/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/index.mdx: -------------------------------------------------------------------------------- 1 | import {ExamplesIndex} from '../components'; 2 | import {toKebapCase} from '../utils/to-kebap-case'; 3 | 4 | 6 | `/images/examples/${item.docId || toKebapCase(item.label)}.jpg` 7 | } 8 | /> 9 | -------------------------------------------------------------------------------- /website/src/examples/map-3d.mdx: -------------------------------------------------------------------------------- 1 | # 3D Maps 2 | 3 | import App from 'website-examples/map-3d/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/map-control.mdx: -------------------------------------------------------------------------------- 1 | # Custom Map Controls 2 | 3 | import App from 'website-examples/map-control/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/marker-clustering.mdx: -------------------------------------------------------------------------------- 1 | # Marker Clustering 2 | 3 | import App from 'website-examples/marker-clustering/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/markers-and-infowindows.mdx: -------------------------------------------------------------------------------- 1 | # Markers and Infowindows 2 | 3 | import App from 'website-examples/markers-and-infowindows/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/examples/multiple-maps.mdx: -------------------------------------------------------------------------------- 1 | # Synchronized Maps 2 | 3 | import App from 'website-examples/multiple-maps/src/app'; 4 | 5 | 6 | -------------------------------------------------------------------------------- /website/src/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | :root { 9 | --ifm-color-primary: #00ade6; 10 | --ifm-color-primary-dark: #009ccf; 11 | --ifm-color-primary-darker: #0093c4; 12 | --ifm-color-primary-darkest: #0079a1; 13 | --ifm-color-primary-light: #00befd; 14 | --ifm-color-primary-lighter: #0ac2ff; 15 | --ifm-color-primary-lightest: #2ccbff; 16 | --ifm-color-white: #ffffff; 17 | --ifm-color-gray-200: #f7fafc; 18 | --ifm-color-gray-300: #ecf2f7; 19 | --ifm-color-gray-400: #e1e8f0; 20 | --ifm-color-gray-500: #cad5e0; 21 | --ifm-color-gray-600: #9eaec0; 22 | --ifm-color-gray-700: #6f8196; 23 | --ifm-color-gray-800: #485668; 24 | --ifm-color-gray-900: #2b3848; 25 | --ifm-color-black: #19202c; 26 | --code-font-size: 95%; 27 | } 28 | main .container { 29 | position: relative; 30 | min-height: calc(100vh - var(--ifm-navbar-height)); 31 | } 32 | .markdown { 33 | position: relative; 34 | } 35 | .menu__link { 36 | line-height: 1.5; 37 | } 38 | .menu__list { 39 | padding-bottom: 0.5rem; 40 | } 41 | 42 | .docusaurus-highlight-code-line { 43 | background-color: rgba(0, 0, 0, 0.1); 44 | display: block; 45 | margin: 0 calc(-1 * var(--pre-padding)); 46 | padding: 0 var(--pre-padding); 47 | } 48 | 49 | html[data-theme='dark'] .docusaurus-highlight-code-line { 50 | background-color: rgba(0, 0, 0, 0.3); 51 | } 52 | 53 | .tooltip { 54 | position: absolute; 55 | padding: 4px; 56 | background: rgba(0, 0, 0, 0.8); 57 | color: #fff; 58 | max-width: 300px; 59 | font-size: 12px; 60 | line-height: 16px; 61 | z-index: 9; 62 | pointer-events: none; 63 | } 64 | -------------------------------------------------------------------------------- /website/src/utils/to-kebap-case.ts: -------------------------------------------------------------------------------- 1 | export function toKebapCase(s: string) { 2 | const matches = s.match( 3 | /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g 4 | ); 5 | 6 | if (!matches) return s; 7 | 8 | return matches.join('-').toLowerCase(); 9 | } 10 | -------------------------------------------------------------------------------- /website/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/favicon.ico -------------------------------------------------------------------------------- /website/static/images/examples/advanced-marker-interaction.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/advanced-marker-interaction.jpg -------------------------------------------------------------------------------- /website/static/images/examples/autocomplete.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/autocomplete.jpg -------------------------------------------------------------------------------- /website/static/images/examples/basic-map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/basic-map.jpg -------------------------------------------------------------------------------- /website/static/images/examples/change-map-styles.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/change-map-styles.jpg -------------------------------------------------------------------------------- /website/static/images/examples/custom-marker-clustering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/custom-marker-clustering.jpg -------------------------------------------------------------------------------- /website/static/images/examples/deckgl-overlay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/deckgl-overlay.jpg -------------------------------------------------------------------------------- /website/static/images/examples/directions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/directions.jpg -------------------------------------------------------------------------------- /website/static/images/examples/drawing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/drawing.jpg -------------------------------------------------------------------------------- /website/static/images/examples/extended-component-library.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/extended-component-library.jpg -------------------------------------------------------------------------------- /website/static/images/examples/geometry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/geometry.jpg -------------------------------------------------------------------------------- /website/static/images/examples/heatmap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/heatmap.jpg -------------------------------------------------------------------------------- /website/static/images/examples/map-3d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/map-3d.jpg -------------------------------------------------------------------------------- /website/static/images/examples/map-control.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/map-control.jpg -------------------------------------------------------------------------------- /website/static/images/examples/marker-clustering.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/marker-clustering.jpg -------------------------------------------------------------------------------- /website/static/images/examples/markers-and-infowindows.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/markers-and-infowindows.jpg -------------------------------------------------------------------------------- /website/static/images/examples/multiple-maps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/examples/multiple-maps.jpg -------------------------------------------------------------------------------- /website/static/images/icon-high-precision.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | high-precision 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /website/static/images/visgl-logo-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/visgl-logo-dark.png -------------------------------------------------------------------------------- /website/static/images/visgl-logo-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/visgl-logo-light.png -------------------------------------------------------------------------------- /website/static/images/visgl-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AaqibhafeezKhan/react-google-maps/1d9d52cc71eef90e72865389e7849bfc333a1936/website/static/images/visgl-logo.png -------------------------------------------------------------------------------- /website/static/scripts/examples.js: -------------------------------------------------------------------------------- 1 | // For the codesandbox examples, the 'process.env.GOOGLE_MAPS_API_KEY' 2 | // gets replaced in the deploy task in the github action 3 | // to contain the valid key for the examples 4 | globalThis.GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY; 5 | --------------------------------------------------------------------------------