Skip to content

Commit ddd1407

Browse files
authored
docs: add basic terra draw example (#909)
1 parent 139d094 commit ddd1407

15 files changed

Lines changed: 629 additions & 0 deletions

examples/examples.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,63 @@
4141
font-size: 11px;
4242
}
4343

44+
.terra-draw-toolbar {
45+
display: flex;
46+
flex-direction: column;
47+
gap: 8px;
48+
padding: 10px;
49+
background: #fff;
50+
border-radius: 4px;
51+
box-shadow: rgba(0, 0, 0, 0.3) 0px 1px 4px -1px;
52+
margin: 12px;
53+
}
54+
55+
.terra-draw-toolbar-group {
56+
display: flex;
57+
flex-direction: column;
58+
gap: 6px;
59+
}
60+
61+
.terra-draw-toolbar-row {
62+
display: flex;
63+
flex-wrap: wrap;
64+
gap: 6px;
65+
}
66+
67+
.terra-draw-button {
68+
border: 1px solid #d5d5d5;
69+
background: #fff;
70+
color: #2e2e2e;
71+
border-radius: 4px;
72+
padding: 4px 10px;
73+
font-size: 12px;
74+
cursor: pointer;
75+
transition:
76+
background 0.15s ease,
77+
color 0.15s ease,
78+
border-color 0.15s ease;
79+
}
80+
81+
.terra-draw-button:hover:not(:disabled) {
82+
border-color: #486865;
83+
color: #486865;
84+
}
85+
86+
.terra-draw-button.active {
87+
background: #486865;
88+
color: #fff;
89+
border-color: #486865;
90+
}
91+
92+
.terra-draw-button:disabled {
93+
opacity: 0.5;
94+
cursor: default;
95+
}
96+
97+
.terra-draw-file-input {
98+
display: none;
99+
}
100+
44101
html[data-theme='dark'] .control-panel {
45102
background: var(--ifm-background-color);
46103
}

examples/terra-draw/README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# TerraDraw Integration Example
2+
3+
This example demonstrates how to integrate TerraDraw with
4+
`@vis.gl/react-google-maps` to add drawing capabilities to a React application.
5+
6+
It includes:
7+
8+
- A `MapControl` toolbar for switching TerraDraw modes
9+
- Basic feature actions (delete last, clear all)
10+
- GeoJSON import and export helpers
11+
12+
## Google Maps Platform API Key
13+
14+
This example does not come with an API key. Running the examples locally requires
15+
a valid API key for the Google Maps Platform.
16+
See [the official documentation][get-api-key] on how to create and configure your own key.
17+
18+
The API key has to be provided via an environment variable `GOOGLE_MAPS_API_KEY`. This can be done by creating a
19+
file named `.env` in the example directory with the following content:
20+
21+
```shell title=".env"
22+
GOOGLE_MAPS_API_KEY="<YOUR API KEY HERE>"
23+
```
24+
25+
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)
26+
27+
## Development
28+
29+
Go into the example-directory and run
30+
31+
```shell
32+
npm install
33+
```
34+
35+
To start the example with the local library run
36+
37+
```shell
38+
npm run start-local
39+
```
40+
41+
The regular `npm start` task is only used for the standalone versions of the example (CodeSandbox for example)
42+
43+
[get-api-key]: https://developers.google.com/maps/documentation/javascript/get-api-key

examples/terra-draw/index.html

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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>TerraDraw Integration Example</title>
9+
<meta
10+
name="description"
11+
content="TerraDraw integration with React Google Maps" />
12+
<style>
13+
body {
14+
margin: 0;
15+
font-family: sans-serif;
16+
}
17+
#app {
18+
width: 100vw;
19+
height: 100vh;
20+
}
21+
</style>
22+
</head>
23+
<body>
24+
<div id="app"></div>
25+
<script type="module">
26+
import '@vis.gl/react-google-maps/examples.css';
27+
import '@vis.gl/react-google-maps/examples.js';
28+
import {renderToDom} from './src/app';
29+
30+
renderToDom(document.querySelector('#app'));
31+
</script>
32+
</body>
33+
</html>

examples/terra-draw/package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"type": "module",
3+
"dependencies": {
4+
"@vis.gl/react-google-maps": "latest",
5+
"react": "^19.0.0",
6+
"react-dom": "^19.0.0",
7+
"terra-draw": "^1.23.1",
8+
"terra-draw-google-maps-adapter": "^1.2.1",
9+
"vite": "^7.1.7"
10+
},
11+
"scripts": {
12+
"start": "vite",
13+
"start-local": "vite --config ../vite.config.local.js",
14+
"build": "vite build"
15+
}
16+
}

examples/terra-draw/src/app.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React 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+
import ControlPanel from './control-panel';
11+
import DrawingControls from './drawing-controls';
12+
import GeoJsonControls from './geojson-controls';
13+
import TerraDrawLayer from './terra-draw-layer';
14+
15+
// Provide the Maps JavaScript API key via GOOGLE_MAPS_API_KEY (see README).
16+
const API_KEY =
17+
globalThis.GOOGLE_MAPS_API_KEY ?? (process.env.GOOGLE_MAPS_API_KEY as string);
18+
19+
const App = () => (
20+
<APIProvider apiKey={API_KEY}>
21+
{/* TerraDrawLayer initializes the draw instance once the map is ready. */}
22+
<TerraDrawLayer>
23+
{draw => (
24+
<>
25+
<Map
26+
defaultZoom={3}
27+
defaultCenter={{lat: 22.54992, lng: 0}}
28+
gestureHandling={'greedy'}
29+
disableDefaultUI={true}
30+
/>
31+
{/* MapControl renders a simple toolbar inside the map UI. */}
32+
<MapControl position={ControlPosition.TOP_LEFT}>
33+
<div className="terra-draw-toolbar">
34+
<DrawingControls draw={draw} />
35+
<GeoJsonControls draw={draw} />
36+
</div>
37+
</MapControl>
38+
<ControlPanel />
39+
</>
40+
)}
41+
</TerraDrawLayer>
42+
</APIProvider>
43+
);
44+
export default App;
45+
46+
export function renderToDom(container: HTMLElement) {
47+
const root = createRoot(container);
48+
49+
root.render(
50+
<React.StrictMode>
51+
<App />
52+
</React.StrictMode>
53+
);
54+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
3+
function ControlPanel() {
4+
return (
5+
<div className="control-panel">
6+
<h3>Terra Draw Integration</h3>
7+
<p>
8+
An example that wires TerraDraw into the react map. Use the toolbar to
9+
switch modes, draw features, and import/export GeoJSON.
10+
</p>
11+
<div className="links">
12+
<a
13+
href="https://codesandbox.io/s/github/visgl/react-google-maps/tree/main/examples/terra-draw"
14+
target="_new">
15+
Try on CodeSandbox ↗
16+
</a>
17+
18+
<a
19+
href="https://github.com/visgl/react-google-maps/tree/main/examples/terra-draw"
20+
target="_new">
21+
View Code ↗
22+
</a>
23+
</div>
24+
</div>
25+
);
26+
}
27+
28+
export default React.memo(ControlPanel);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import * as React from 'react';
2+
import type {TerraDraw} from 'terra-draw';
3+
4+
import {DRAWING_MODE_BUTTONS, TerraDrawModeId} from './terra-draw-config';
5+
6+
type DrawingControlsProps = {
7+
draw: TerraDraw | null;
8+
};
9+
10+
const DEFAULT_MODE: TerraDrawModeId = 'point';
11+
12+
const DrawingControls = ({draw}: DrawingControlsProps) => {
13+
const [activeMode, setActiveMode] = React.useState<TerraDrawModeId>('static');
14+
15+
React.useEffect(() => {
16+
if (!draw || activeMode !== 'static') return;
17+
18+
// Start in a sensible default mode once TerraDraw is ready.
19+
draw.setMode(DEFAULT_MODE);
20+
setActiveMode(DEFAULT_MODE);
21+
}, [draw, activeMode]);
22+
23+
const handleModeChange = (modeId: TerraDrawModeId) => {
24+
if (!draw) return;
25+
26+
draw.setMode(modeId);
27+
setActiveMode(modeId);
28+
};
29+
30+
const handleClear = () => {
31+
if (!draw) return;
32+
33+
draw.clear();
34+
draw.setMode('static');
35+
setActiveMode('static');
36+
};
37+
38+
const handleDeleteLast = () => {
39+
if (!draw) return;
40+
41+
const snapshot = draw.getSnapshot();
42+
const lastFeature = snapshot[snapshot.length - 1];
43+
44+
if (lastFeature?.id) {
45+
draw.removeFeatures([lastFeature.id]);
46+
}
47+
};
48+
49+
return (
50+
<div className="terra-draw-toolbar-group">
51+
<div className="terra-draw-toolbar-row">
52+
{DRAWING_MODE_BUTTONS.map(button => (
53+
<button
54+
key={button.id}
55+
type="button"
56+
className={`terra-draw-button ${
57+
activeMode === button.id ? 'active' : ''
58+
}`}
59+
onClick={() => handleModeChange(button.id)}
60+
disabled={!draw}>
61+
{button.label}
62+
</button>
63+
))}
64+
</div>
65+
<div className="terra-draw-toolbar-row">
66+
<button
67+
type="button"
68+
className="terra-draw-button"
69+
onClick={handleDeleteLast}
70+
disabled={!draw}>
71+
Delete Last
72+
</button>
73+
<button
74+
type="button"
75+
className="terra-draw-button"
76+
onClick={handleClear}
77+
disabled={!draw}>
78+
Clear All
79+
</button>
80+
</div>
81+
</div>
82+
);
83+
};
84+
85+
export default React.memo(DrawingControls);

0 commit comments

Comments
 (0)