-
Notifications
You must be signed in to change notification settings - Fork 118
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add example for drawing tools (#220)
- Loading branch information
1 parent
69b2373
commit 75e91c4
Showing
14 changed files
with
646 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Drawing Tools Example | ||
|
||
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. | ||
|
||
## Google Maps API key | ||
|
||
This example does not come with an API key. Running the examples locally requires a valid API key for the Google Maps Platform. | ||
See [the official documentation][get-api-key] on how to create and configure your own key. | ||
|
||
The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a | ||
file named `.env` in the example directory with the following content: | ||
|
||
```shell title=".env" | ||
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>" | ||
``` | ||
|
||
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) | ||
|
||
## Development | ||
|
||
Go into the example-directory and run | ||
|
||
```shell | ||
npm install | ||
``` | ||
|
||
To start the example with the local library run | ||
|
||
```shell | ||
npm run start-local | ||
``` | ||
|
||
The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example) | ||
|
||
[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key | ||
[drawing-manager]: https://developers.google.com/maps/documentation/javascript/drawinglayer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<!doctype html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8" /> | ||
<meta | ||
name="viewport" | ||
content="width=device-width, initial-scale=1.0, user-scalable=no" /> | ||
<title>Drawing Tools Example</title> | ||
<meta name="description" content="Drawing Example" /> | ||
<style> | ||
body { | ||
margin: 0; | ||
font-family: sans-serif; | ||
} | ||
#app { | ||
width: 100vw; | ||
height: 100vh; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<div id="app"></div> | ||
<script type="module"> | ||
import '@vis.gl/react-google-maps/examples.css'; | ||
import '@vis.gl/react-google-maps/examples.js'; | ||
import {renderToDom} from './src/app'; | ||
|
||
renderToDom(document.querySelector('#app')); | ||
</script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
{ | ||
"type": "module", | ||
"dependencies": { | ||
"@vis.gl/react-google-maps": "*", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"vite": "^5.0.4" | ||
}, | ||
"scripts": { | ||
"start": "vite", | ||
"start-local": "vite --config ../vite.config.local.js", | ||
"build": "vite build" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import React from 'react'; | ||
import {createRoot} from 'react-dom/client'; | ||
import { | ||
APIProvider, | ||
ControlPosition, | ||
Map, | ||
MapControl | ||
} from '@vis.gl/react-google-maps'; | ||
|
||
import {UndoRedoControl} from './undo-redo-control'; | ||
import {useDrawingManager} from './use-drawing-manager'; | ||
import ControlPanel from './control-panel'; | ||
|
||
const API_KEY = | ||
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string); | ||
|
||
const App = () => { | ||
const drawingManager = useDrawingManager(); | ||
|
||
return ( | ||
<> | ||
<Map | ||
defaultZoom={3} | ||
defaultCenter={{lat: 22.54992, lng: 0}} | ||
gestureHandling={'greedy'} | ||
disableDefaultUI={true} | ||
/> | ||
|
||
<ControlPanel /> | ||
|
||
<MapControl position={ControlPosition.TOP_CENTER}> | ||
<UndoRedoControl drawingManager={drawingManager} /> | ||
</MapControl> | ||
</> | ||
); | ||
}; | ||
|
||
export default App; | ||
|
||
export function renderToDom(container: HTMLElement) { | ||
const root = createRoot(container); | ||
|
||
root.render( | ||
<APIProvider apiKey={API_KEY}> | ||
<App /> | ||
</APIProvider> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import * as React from 'react'; | ||
|
||
function ControlPanel() { | ||
return ( | ||
<div className="control-panel"> | ||
<h3>Drawing Tools Example</h3> | ||
<p> | ||
Shows how to use the Google Maps drawing tools and implements an | ||
undo/redo flow to show how to integrate the drawing manager and its | ||
events into the state of a react-application. | ||
</p> | ||
<div className="links"> | ||
<a | ||
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/drawing" | ||
target="_new"> | ||
Try on CodeSandbox ↗ | ||
</a> | ||
|
||
<a | ||
href="https://github.com/visgl/react-google-maps/tree/main/examples/drawing" | ||
target="_new"> | ||
View Code ↗ | ||
</a> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default React.memo(ControlPanel); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
export type OverlayGeometry = | ||
| google.maps.Marker | ||
| google.maps.Polygon | ||
| google.maps.Polyline | ||
| google.maps.Rectangle | ||
| google.maps.Circle; | ||
|
||
export interface DrawResult { | ||
type: google.maps.drawing.OverlayType; | ||
overlay: OverlayGeometry; | ||
} | ||
|
||
export interface Snapshot { | ||
radius?: number; | ||
center?: google.maps.LatLngLiteral; | ||
position?: google.maps.LatLngLiteral; | ||
path?: Array<google.maps.LatLng>; | ||
bounds?: google.maps.LatLngBoundsLiteral; | ||
} | ||
|
||
export interface Overlay { | ||
type: google.maps.drawing.OverlayType; | ||
geometry: OverlayGeometry; | ||
snapshot: Snapshot; | ||
} | ||
|
||
export interface State { | ||
past: Array<Array<Overlay>>; | ||
now: Array<Overlay>; | ||
future: Array<Array<Overlay>>; | ||
} | ||
|
||
export enum DrawingActionKind { | ||
SET_OVERLAY = 'SET_OVERLAY', | ||
UPDATE_OVERLAYS = 'UPDATE_OVERLAYS', | ||
UNDO = 'UNDO', | ||
REDO = 'REDO' | ||
} | ||
|
||
export interface ActionWithTypeOnly { | ||
type: Exclude<DrawingActionKind, DrawingActionKind.SET_OVERLAY>; | ||
} | ||
|
||
export interface SetOverlayAction { | ||
type: DrawingActionKind.SET_OVERLAY; | ||
payload: DrawResult; | ||
} | ||
|
||
export type Action = ActionWithTypeOnly | SetOverlayAction; | ||
|
||
export function isCircle( | ||
overlay: OverlayGeometry | ||
): overlay is google.maps.Circle { | ||
return (overlay as google.maps.Circle).getCenter !== undefined; | ||
} | ||
|
||
export function isMarker( | ||
overlay: OverlayGeometry | ||
): overlay is google.maps.Marker { | ||
return (overlay as google.maps.Marker).getPosition !== undefined; | ||
} | ||
|
||
export function isPolygon( | ||
overlay: OverlayGeometry | ||
): overlay is google.maps.Polygon { | ||
return (overlay as google.maps.Polygon).getPath !== undefined; | ||
} | ||
|
||
export function isPolyline( | ||
overlay: OverlayGeometry | ||
): overlay is google.maps.Polyline { | ||
return (overlay as google.maps.Polyline).getPath !== undefined; | ||
} | ||
|
||
export function isRectangle( | ||
overlay: OverlayGeometry | ||
): overlay is google.maps.Rectangle { | ||
return (overlay as google.maps.Rectangle).getBounds !== undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React, {useReducer, useRef} from 'react'; | ||
import {useMap} from '@vis.gl/react-google-maps'; | ||
|
||
import reducer, { | ||
useDrawingManagerEvents, | ||
useOverlaySnapshots | ||
} from './undo-redo'; | ||
|
||
import {DrawingActionKind} from './types'; | ||
|
||
interface Props { | ||
drawingManager: google.maps.drawing.DrawingManager | null; | ||
} | ||
|
||
export const UndoRedoControl = ({drawingManager}: Props) => { | ||
const map = useMap(); | ||
|
||
const [state, dispatch] = useReducer(reducer, { | ||
past: [], | ||
now: [], | ||
future: [] | ||
}); | ||
|
||
// We need this ref to prevent infinite loops in certain cases. | ||
// For example when the radius of circle is set via code (and not by user interaction) | ||
// the radius_changed event gets triggered again. This would cause an infinite loop. | ||
// This solution can be improved by comparing old vs. new values. For now we turn | ||
// off the "updating" when snapshot changes are applied back to the overlays. | ||
const overlaysShouldUpdateRef = useRef<boolean>(false); | ||
|
||
useDrawingManagerEvents(drawingManager, overlaysShouldUpdateRef, dispatch); | ||
useOverlaySnapshots(map, state, overlaysShouldUpdateRef); | ||
|
||
return ( | ||
<div className="drawing-history"> | ||
<button | ||
onClick={() => dispatch({type: DrawingActionKind.UNDO})} | ||
disabled={!state.past.length}> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
height="24" | ||
viewBox="0 -960 960 960" | ||
width="24"> | ||
<path d="M280-200v-80h284q63 0 109.5-40T720-420q0-60-46.5-100T564-560H312l104 104-56 56-200-200 200-200 56 56-104 104h252q97 0 166.5 63T800-420q0 94-69.5 157T564-200H280Z" /> | ||
</svg> | ||
</button> | ||
<button | ||
onClick={() => dispatch({type: DrawingActionKind.REDO})} | ||
disabled={!state.future.length}> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
height="24" | ||
viewBox="0 -960 960 960" | ||
width="24"> | ||
<path d="M396-200q-97 0-166.5-63T160-420q0-94 69.5-157T396-640h252L544-744l56-56 200 200-200 200-56-56 104-104H396q-63 0-109.5 40T240-420q0 60 46.5 100T396-280h284v80H396Z" /> | ||
</svg> | ||
</button> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.