Skip to content

Commit 75e91c4

Browse files
authored
feat: add example for drawing tools (#220)
1 parent 69b2373 commit 75e91c4

File tree

14 files changed

+646
-0
lines changed

14 files changed

+646
-0
lines changed

examples/drawing/README.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 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="<YOUR API KEY HERE>"
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

examples/drawing/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1.0, user-scalable=no" />
8+
<title>Drawing Tools Example</title>
9+
<meta name="description" content="Drawing Example" />
10+
<style>
11+
body {
12+
margin: 0;
13+
font-family: sans-serif;
14+
}
15+
#app {
16+
width: 100vw;
17+
height: 100vh;
18+
}
19+
</style>
20+
</head>
21+
<body>
22+
<div id="app"></div>
23+
<script type="module">
24+
import '@vis.gl/react-google-maps/examples.css';
25+
import '@vis.gl/react-google-maps/examples.js';
26+
import {renderToDom} from './src/app';
27+
28+
renderToDom(document.querySelector('#app'));
29+
</script>
30+
</body>
31+
</html>

examples/drawing/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"@vis.gl/react-google-maps": "*",
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+
}

examples/drawing/src/app.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import {createRoot} from 'react-dom/client';
3+
import {
4+
APIProvider,
5+
ControlPosition,
6+
Map,
7+
MapControl
8+
} from '@vis.gl/react-google-maps';
9+
10+
import {UndoRedoControl} from './undo-redo-control';
11+
import {useDrawingManager} from './use-drawing-manager';
12+
import ControlPanel from './control-panel';
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 drawingManager = useDrawingManager();
19+
20+
return (
21+
<>
22+
<Map
23+
defaultZoom={3}
24+
defaultCenter={{lat: 22.54992, lng: 0}}
25+
gestureHandling={'greedy'}
26+
disableDefaultUI={true}
27+
/>
28+
29+
<ControlPanel />
30+
31+
<MapControl position={ControlPosition.TOP_CENTER}>
32+
<UndoRedoControl drawingManager={drawingManager} />
33+
</MapControl>
34+
</>
35+
);
36+
};
37+
38+
export default App;
39+
40+
export function renderToDom(container: HTMLElement) {
41+
const root = createRoot(container);
42+
43+
root.render(
44+
<APIProvider apiKey={API_KEY}>
45+
<App />
46+
</APIProvider>
47+
);
48+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from 'react';
2+
3+
function ControlPanel() {
4+
return (
5+
<div className="control-panel">
6+
<h3>Drawing Tools Example</h3>
7+
<p>
8+
Shows how to use the Google Maps drawing tools and implements an
9+
undo/redo flow to show how to integrate the drawing manager and its
10+
events into the state of a react-application.
11+
</p>
12+
<div className="links">
13+
<a
14+
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/drawing"
15+
target="_new">
16+
Try on CodeSandbox ↗
17+
</a>
18+
19+
<a
20+
href="https://github.com/visgl/react-google-maps/tree/main/examples/drawing"
21+
target="_new">
22+
View Code ↗
23+
</a>
24+
</div>
25+
</div>
26+
);
27+
}
28+
29+
export default React.memo(ControlPanel);

examples/drawing/src/types.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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<google.maps.LatLng>;
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<Array<Overlay>>;
29+
now: Array<Overlay>;
30+
future: Array<Array<Overlay>>;
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<DrawingActionKind, DrawingActionKind.SET_OVERLAY>;
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+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import React, {useReducer, useRef} from 'react';
2+
import {useMap} from '@vis.gl/react-google-maps';
3+
4+
import reducer, {
5+
useDrawingManagerEvents,
6+
useOverlaySnapshots
7+
} from './undo-redo';
8+
9+
import {DrawingActionKind} from './types';
10+
11+
interface Props {
12+
drawingManager: google.maps.drawing.DrawingManager | null;
13+
}
14+
15+
export const UndoRedoControl = ({drawingManager}: Props) => {
16+
const map = useMap();
17+
18+
const [state, dispatch] = useReducer(reducer, {
19+
past: [],
20+
now: [],
21+
future: []
22+
});
23+
24+
// We need this ref to prevent infinite loops in certain cases.
25+
// For example when the radius of circle is set via code (and not by user interaction)
26+
// the radius_changed event gets triggered again. This would cause an infinite loop.
27+
// This solution can be improved by comparing old vs. new values. For now we turn
28+
// off the "updating" when snapshot changes are applied back to the overlays.
29+
const overlaysShouldUpdateRef = useRef<boolean>(false);
30+
31+
useDrawingManagerEvents(drawingManager, overlaysShouldUpdateRef, dispatch);
32+
useOverlaySnapshots(map, state, overlaysShouldUpdateRef);
33+
34+
return (
35+
<div className="drawing-history">
36+
<button
37+
onClick={() => dispatch({type: DrawingActionKind.UNDO})}
38+
disabled={!state.past.length}>
39+
<svg
40+
xmlns="http://www.w3.org/2000/svg"
41+
height="24"
42+
viewBox="0 -960 960 960"
43+
width="24">
44+
<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" />
45+
</svg>
46+
</button>
47+
<button
48+
onClick={() => dispatch({type: DrawingActionKind.REDO})}
49+
disabled={!state.future.length}>
50+
<svg
51+
xmlns="http://www.w3.org/2000/svg"
52+
height="24"
53+
viewBox="0 -960 960 960"
54+
width="24">
55+
<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" />
56+
</svg>
57+
</button>
58+
</div>
59+
);
60+
};

0 commit comments

Comments
 (0)