0
# Drawing Tools
1
2
Google Maps drawing tools component for creating and editing geometric shapes interactively on the map. Enables users to draw markers, polylines, polygons, circles, and rectangles.
3
4
## Capabilities
5
6
### DrawingManager
7
8
Component that provides interactive drawing tools for creating shapes on the map.
9
10
```javascript { .api }
11
/**
12
* Drawing manager component for interactive shape creation
13
*/
14
class DrawingManager extends Component<DrawingManagerProps> {
15
/** Returns the current drawing mode */
16
getDrawingMode(): google.maps.drawing.OverlayType;
17
}
18
19
interface DrawingManagerProps {
20
// Default props
21
defaultDrawingMode?: google.maps.drawing.OverlayType;
22
defaultOptions?: google.maps.drawing.DrawingManagerOptions;
23
24
// Controlled props
25
drawingMode?: google.maps.drawing.OverlayType;
26
options?: google.maps.drawing.DrawingManagerOptions;
27
28
// Event handlers
29
onCircleComplete?(c: google.maps.Circle): void;
30
onMarkerComplete?(m: google.maps.Marker): void;
31
onOverlayComplete?(e: google.maps.drawing.OverlayCompleteEvent): void;
32
onPolygonComplete?(p: google.maps.Polygon): void;
33
onPolylineComplete?(p: google.maps.Polyline): void;
34
onRectangleComplete?(r: google.maps.Rectangle): void;
35
}
36
```
37
38
**Usage Example:**
39
40
```javascript
41
import DrawingManager from "react-google-maps/lib/components/drawing/DrawingManager";
42
43
const DrawingMap = () => {
44
const [drawingMode, setDrawingMode] = useState(null);
45
const [shapes, setShapes] = useState([]);
46
47
const onOverlayComplete = (event) => {
48
const newShape = {
49
id: Date.now(),
50
type: event.type,
51
overlay: event.overlay
52
};
53
54
setShapes(prevShapes => [...prevShapes, newShape]);
55
56
// Disable drawing mode after completing a shape
57
setDrawingMode(null);
58
};
59
60
const deleteShape = (shapeId) => {
61
setShapes(prevShapes => {
62
const shapeToDelete = prevShapes.find(shape => shape.id === shapeId);
63
if (shapeToDelete) {
64
shapeToDelete.overlay.setMap(null); // Remove from map
65
}
66
return prevShapes.filter(shape => shape.id !== shapeId);
67
});
68
};
69
70
return (
71
<div>
72
{/* Drawing controls */}
73
<div style={{ padding: '10px', backgroundColor: '#f5f5f5' }}>
74
<button
75
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.MARKER)}
76
style={{ margin: '0 5px', padding: '8px 12px' }}
77
>
78
Draw Marker
79
</button>
80
<button
81
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.POLYLINE)}
82
style={{ margin: '0 5px', padding: '8px 12px' }}
83
>
84
Draw Line
85
</button>
86
<button
87
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.POLYGON)}
88
style={{ margin: '0 5px', padding: '8px 12px' }}
89
>
90
Draw Polygon
91
</button>
92
<button
93
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.CIRCLE)}
94
style={{ margin: '0 5px', padding: '8px 12px' }}
95
>
96
Draw Circle
97
</button>
98
<button
99
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE)}
100
style={{ margin: '0 5px', padding: '8px 12px' }}
101
>
102
Draw Rectangle
103
</button>
104
<button
105
onClick={() => setDrawingMode(null)}
106
style={{ margin: '0 5px', padding: '8px 12px', backgroundColor: '#ff6b6b' }}
107
>
108
Stop Drawing
109
</button>
110
</div>
111
112
<GoogleMap
113
defaultZoom={10}
114
defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
115
style={{ height: '500px' }}
116
>
117
<DrawingManager
118
drawingMode={drawingMode}
119
options={{
120
drawingControl: true,
121
drawingControlOptions: {
122
position: google.maps.ControlPosition.TOP_CENTER,
123
drawingModes: [
124
google.maps.drawing.OverlayType.MARKER,
125
google.maps.drawing.OverlayType.CIRCLE,
126
google.maps.drawing.OverlayType.POLYGON,
127
google.maps.drawing.OverlayType.POLYLINE,
128
google.maps.drawing.OverlayType.RECTANGLE,
129
],
130
},
131
markerOptions: {
132
icon: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
133
},
134
circleOptions: {
135
fillColor: '#ffff00',
136
fillOpacity: 0.3,
137
strokeWeight: 2,
138
clickable: false,
139
editable: true,
140
zIndex: 1,
141
},
142
polygonOptions: {
143
fillColor: '#ff0000',
144
fillOpacity: 0.3,
145
strokeWeight: 2,
146
clickable: false,
147
editable: true,
148
zIndex: 1,
149
},
150
polylineOptions: {
151
strokeColor: '#0000ff',
152
strokeWeight: 3,
153
clickable: false,
154
editable: true,
155
zIndex: 1,
156
},
157
rectangleOptions: {
158
fillColor: '#00ff00',
159
fillOpacity: 0.3,
160
strokeWeight: 2,
161
clickable: false,
162
editable: true,
163
zIndex: 1,
164
},
165
}}
166
onOverlayComplete={onOverlayComplete}
167
onMarkerComplete={(marker) => {
168
console.log('Marker created at:', marker.getPosition().toJSON());
169
}}
170
onCircleComplete={(circle) => {
171
console.log('Circle created with radius:', circle.getRadius());
172
}}
173
onPolygonComplete={(polygon) => {
174
console.log('Polygon created with', polygon.getPath().getLength(), 'vertices');
175
}}
176
onPolylineComplete={(polyline) => {
177
console.log('Polyline created with', polyline.getPath().getLength(), 'points');
178
}}
179
onRectangleComplete={(rectangle) => {
180
console.log('Rectangle created with bounds:', rectangle.getBounds().toJSON());
181
}}
182
/>
183
</GoogleMap>
184
185
{/* Shape list */}
186
<div style={{ padding: '10px' }}>
187
<h3>Created Shapes ({shapes.length})</h3>
188
{shapes.map((shape) => (
189
<div key={shape.id} style={{
190
display: 'flex',
191
justifyContent: 'space-between',
192
alignItems: 'center',
193
padding: '8px',
194
border: '1px solid #ddd',
195
margin: '4px 0'
196
}}>
197
<span>{shape.type} (ID: {shape.id})</span>
198
<button
199
onClick={() => deleteShape(shape.id)}
200
style={{ backgroundColor: '#ff6b6b', color: 'white', border: 'none', padding: '4px 8px' }}
201
>
202
Delete
203
</button>
204
</div>
205
))}
206
</div>
207
</div>
208
);
209
};
210
```
211
212
## Drawing Manager Configuration
213
214
### Drawing Modes
215
216
Available drawing modes from `google.maps.drawing.OverlayType`:
217
218
```javascript
219
const drawingModes = {
220
MARKER: google.maps.drawing.OverlayType.MARKER,
221
CIRCLE: google.maps.drawing.OverlayType.CIRCLE,
222
POLYGON: google.maps.drawing.OverlayType.POLYGON,
223
POLYLINE: google.maps.drawing.OverlayType.POLYLINE,
224
RECTANGLE: google.maps.drawing.OverlayType.RECTANGLE
225
};
226
```
227
228
### Shape Styling Options
229
230
Configure the appearance of each shape type:
231
232
```javascript
233
const drawingOptions = {
234
drawingControl: true,
235
drawingControlOptions: {
236
position: google.maps.ControlPosition.TOP_CENTER,
237
drawingModes: [
238
google.maps.drawing.OverlayType.MARKER,
239
google.maps.drawing.OverlayType.CIRCLE,
240
google.maps.drawing.OverlayType.POLYGON,
241
google.maps.drawing.OverlayType.POLYLINE,
242
google.maps.drawing.OverlayType.RECTANGLE,
243
],
244
},
245
markerOptions: {
246
icon: {
247
url: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
248
scaledSize: { width: 32, height: 32 }
249
},
250
draggable: true
251
},
252
circleOptions: {
253
fillColor: '#ffff00',
254
fillOpacity: 0.3,
255
strokeColor: '#ffff00',
256
strokeWeight: 2,
257
clickable: true,
258
editable: true,
259
draggable: true,
260
zIndex: 1,
261
},
262
polygonOptions: {
263
fillColor: '#ff0000',
264
fillOpacity: 0.3,
265
strokeColor: '#ff0000',
266
strokeWeight: 2,
267
clickable: true,
268
editable: true,
269
draggable: true,
270
zIndex: 1,
271
},
272
polylineOptions: {
273
strokeColor: '#0000ff',
274
strokeWeight: 3,
275
clickable: true,
276
editable: true,
277
draggable: true,
278
zIndex: 1,
279
},
280
rectangleOptions: {
281
fillColor: '#00ff00',
282
fillOpacity: 0.3,
283
strokeColor: '#00ff00',
284
strokeWeight: 2,
285
clickable: true,
286
editable: true,
287
draggable: true,
288
zIndex: 1,
289
},
290
};
291
```
292
293
## Advanced Drawing Patterns
294
295
### Shape Data Export
296
297
Export drawn shapes as GeoJSON:
298
299
```javascript
300
const exportShapesToGeoJSON = (shapes) => {
301
const features = shapes.map(shape => {
302
let geometry;
303
304
switch (shape.type) {
305
case google.maps.drawing.OverlayType.MARKER:
306
const position = shape.overlay.getPosition();
307
geometry = {
308
type: "Point",
309
coordinates: [position.lng(), position.lat()]
310
};
311
break;
312
313
case google.maps.drawing.OverlayType.POLYLINE:
314
const path = shape.overlay.getPath().getArray();
315
geometry = {
316
type: "LineString",
317
coordinates: path.map(point => [point.lng(), point.lat()])
318
};
319
break;
320
321
case google.maps.drawing.OverlayType.POLYGON:
322
const polygonPath = shape.overlay.getPath().getArray();
323
geometry = {
324
type: "Polygon",
325
coordinates: [polygonPath.map(point => [point.lng(), point.lat()])]
326
};
327
break;
328
329
case google.maps.drawing.OverlayType.CIRCLE:
330
const center = shape.overlay.getCenter();
331
const radius = shape.overlay.getRadius();
332
// Note: GeoJSON doesn't support circles directly
333
geometry = {
334
type: "Point",
335
coordinates: [center.lng(), center.lat()],
336
properties: { radius: radius }
337
};
338
break;
339
340
case google.maps.drawing.OverlayType.RECTANGLE:
341
const bounds = shape.overlay.getBounds();
342
const ne = bounds.getNorthEast();
343
const sw = bounds.getSouthWest();
344
geometry = {
345
type: "Polygon",
346
coordinates: [[
347
[sw.lng(), sw.lat()],
348
[ne.lng(), sw.lat()],
349
[ne.lng(), ne.lat()],
350
[sw.lng(), ne.lat()],
351
[sw.lng(), sw.lat()]
352
]]
353
};
354
break;
355
}
356
357
return {
358
type: "Feature",
359
properties: {
360
id: shape.id,
361
type: shape.type
362
},
363
geometry: geometry
364
};
365
});
366
367
return {
368
type: "FeatureCollection",
369
features: features
370
};
371
};
372
```
373
374
### Drawing with Validation
375
376
Add validation rules for drawn shapes:
377
378
```javascript
379
const ValidatedDrawingManager = () => {
380
const [shapes, setShapes] = useState([]);
381
const [errors, setErrors] = useState([]);
382
383
const validateShape = (shape, type) => {
384
const validation = { isValid: true, errors: [] };
385
386
switch (type) {
387
case google.maps.drawing.OverlayType.POLYGON:
388
const path = shape.getPath().getArray();
389
if (path.length < 3) {
390
validation.isValid = false;
391
validation.errors.push("Polygon must have at least 3 vertices");
392
}
393
394
// Check for minimum area
395
const area = google.maps.geometry.spherical.computeArea(path);
396
if (area < 1000) { // 1000 square meters
397
validation.isValid = false;
398
validation.errors.push("Polygon area must be at least 1000 sq meters");
399
}
400
break;
401
402
case google.maps.drawing.OverlayType.CIRCLE:
403
const radius = shape.getRadius();
404
if (radius < 50) { // 50 meters minimum
405
validation.isValid = false;
406
validation.errors.push("Circle radius must be at least 50 meters");
407
}
408
break;
409
410
case google.maps.drawing.OverlayType.POLYLINE:
411
const polylinePath = shape.getPath().getArray();
412
if (polylinePath.length < 2) {
413
validation.isValid = false;
414
validation.errors.push("Polyline must have at least 2 points");
415
}
416
break;
417
}
418
419
return validation;
420
};
421
422
const onOverlayComplete = (event) => {
423
const validation = validateShape(event.overlay, event.type);
424
425
if (validation.isValid) {
426
const newShape = {
427
id: Date.now(),
428
type: event.type,
429
overlay: event.overlay
430
};
431
setShapes(prevShapes => [...prevShapes, newShape]);
432
setErrors([]);
433
} else {
434
// Remove invalid shape from map
435
event.overlay.setMap(null);
436
setErrors(validation.errors);
437
}
438
};
439
440
return (
441
<div>
442
{errors.length > 0 && (
443
<div style={{
444
background: '#ffebee',
445
color: '#c62828',
446
padding: '10px',
447
margin: '10px 0'
448
}}>
449
<strong>Drawing Errors:</strong>
450
<ul>
451
{errors.map((error, index) => (
452
<li key={index}>{error}</li>
453
))}
454
</ul>
455
</div>
456
)}
457
458
<GoogleMap defaultZoom={10} defaultCenter={{ lat: 37.7749, lng: -122.4194 }}>
459
<DrawingManager
460
options={drawingOptions}
461
onOverlayComplete={onOverlayComplete}
462
/>
463
</GoogleMap>
464
</div>
465
);
466
};
467
```
468
469
### Drawing with Snapping
470
471
Enable snapping to existing shapes or grid:
472
473
```javascript
474
const SnappingDrawingManager = () => {
475
const [snapToGrid, setSnapToGrid] = useState(true);
476
const gridSize = 0.001; // ~100m grid
477
478
const snapToGridPoint = (latLng) => {
479
if (!snapToGrid) return latLng;
480
481
const lat = Math.round(latLng.lat() / gridSize) * gridSize;
482
const lng = Math.round(latLng.lng() / gridSize) * gridSize;
483
484
return new google.maps.LatLng(lat, lng);
485
};
486
487
const onOverlayComplete = (event) => {
488
if (snapToGrid) {
489
switch (event.type) {
490
case google.maps.drawing.OverlayType.MARKER:
491
const snappedPosition = snapToGridPoint(event.overlay.getPosition());
492
event.overlay.setPosition(snappedPosition);
493
break;
494
495
case google.maps.drawing.OverlayType.POLYGON:
496
const path = event.overlay.getPath();
497
for (let i = 0; i < path.getLength(); i++) {
498
const snappedPoint = snapToGridPoint(path.getAt(i));
499
path.setAt(i, snappedPoint);
500
}
501
break;
502
}
503
}
504
505
// Continue with normal shape handling...
506
};
507
508
return (
509
<div>
510
<label>
511
<input
512
type="checkbox"
513
checked={snapToGrid}
514
onChange={(e) => setSnapToGrid(e.target.checked)}
515
/>
516
Snap to Grid
517
</label>
518
519
<GoogleMap defaultZoom={15} defaultCenter={{ lat: 37.7749, lng: -122.4194 }}>
520
<DrawingManager
521
options={drawingOptions}
522
onOverlayComplete={onOverlayComplete}
523
/>
524
</GoogleMap>
525
</div>
526
);
527
};
528
```