React.js Google Maps integration component library providing comprehensive Google Maps functionality through React components
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
Component that provides interactive drawing tools for creating shapes on the map.
/**
* Drawing manager component for interactive shape creation
*/
class DrawingManager extends Component<DrawingManagerProps> {
/** Returns the current drawing mode */
getDrawingMode(): google.maps.drawing.OverlayType;
}
interface DrawingManagerProps {
// Default props
defaultDrawingMode?: google.maps.drawing.OverlayType;
defaultOptions?: google.maps.drawing.DrawingManagerOptions;
// Controlled props
drawingMode?: google.maps.drawing.OverlayType;
options?: google.maps.drawing.DrawingManagerOptions;
// Event handlers
onCircleComplete?(c: google.maps.Circle): void;
onMarkerComplete?(m: google.maps.Marker): void;
onOverlayComplete?(e: google.maps.drawing.OverlayCompleteEvent): void;
onPolygonComplete?(p: google.maps.Polygon): void;
onPolylineComplete?(p: google.maps.Polyline): void;
onRectangleComplete?(r: google.maps.Rectangle): void;
}Usage Example:
import DrawingManager from "react-google-maps/lib/components/drawing/DrawingManager";
const DrawingMap = () => {
const [drawingMode, setDrawingMode] = useState(null);
const [shapes, setShapes] = useState([]);
const onOverlayComplete = (event) => {
const newShape = {
id: Date.now(),
type: event.type,
overlay: event.overlay
};
setShapes(prevShapes => [...prevShapes, newShape]);
// Disable drawing mode after completing a shape
setDrawingMode(null);
};
const deleteShape = (shapeId) => {
setShapes(prevShapes => {
const shapeToDelete = prevShapes.find(shape => shape.id === shapeId);
if (shapeToDelete) {
shapeToDelete.overlay.setMap(null); // Remove from map
}
return prevShapes.filter(shape => shape.id !== shapeId);
});
};
return (
<div>
{/* Drawing controls */}
<div style={{ padding: '10px', backgroundColor: '#f5f5f5' }}>
<button
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.MARKER)}
style={{ margin: '0 5px', padding: '8px 12px' }}
>
Draw Marker
</button>
<button
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.POLYLINE)}
style={{ margin: '0 5px', padding: '8px 12px' }}
>
Draw Line
</button>
<button
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.POLYGON)}
style={{ margin: '0 5px', padding: '8px 12px' }}
>
Draw Polygon
</button>
<button
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.CIRCLE)}
style={{ margin: '0 5px', padding: '8px 12px' }}
>
Draw Circle
</button>
<button
onClick={() => setDrawingMode(google.maps.drawing.OverlayType.RECTANGLE)}
style={{ margin: '0 5px', padding: '8px 12px' }}
>
Draw Rectangle
</button>
<button
onClick={() => setDrawingMode(null)}
style={{ margin: '0 5px', padding: '8px 12px', backgroundColor: '#ff6b6b' }}
>
Stop Drawing
</button>
</div>
<GoogleMap
defaultZoom={10}
defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
style={{ height: '500px' }}
>
<DrawingManager
drawingMode={drawingMode}
options={{
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.MARKER,
google.maps.drawing.OverlayType.CIRCLE,
google.maps.drawing.OverlayType.POLYGON,
google.maps.drawing.OverlayType.POLYLINE,
google.maps.drawing.OverlayType.RECTANGLE,
],
},
markerOptions: {
icon: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
},
circleOptions: {
fillColor: '#ffff00',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: true,
zIndex: 1,
},
polygonOptions: {
fillColor: '#ff0000',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: true,
zIndex: 1,
},
polylineOptions: {
strokeColor: '#0000ff',
strokeWeight: 3,
clickable: false,
editable: true,
zIndex: 1,
},
rectangleOptions: {
fillColor: '#00ff00',
fillOpacity: 0.3,
strokeWeight: 2,
clickable: false,
editable: true,
zIndex: 1,
},
}}
onOverlayComplete={onOverlayComplete}
onMarkerComplete={(marker) => {
console.log('Marker created at:', marker.getPosition().toJSON());
}}
onCircleComplete={(circle) => {
console.log('Circle created with radius:', circle.getRadius());
}}
onPolygonComplete={(polygon) => {
console.log('Polygon created with', polygon.getPath().getLength(), 'vertices');
}}
onPolylineComplete={(polyline) => {
console.log('Polyline created with', polyline.getPath().getLength(), 'points');
}}
onRectangleComplete={(rectangle) => {
console.log('Rectangle created with bounds:', rectangle.getBounds().toJSON());
}}
/>
</GoogleMap>
{/* Shape list */}
<div style={{ padding: '10px' }}>
<h3>Created Shapes ({shapes.length})</h3>
{shapes.map((shape) => (
<div key={shape.id} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '8px',
border: '1px solid #ddd',
margin: '4px 0'
}}>
<span>{shape.type} (ID: {shape.id})</span>
<button
onClick={() => deleteShape(shape.id)}
style={{ backgroundColor: '#ff6b6b', color: 'white', border: 'none', padding: '4px 8px' }}
>
Delete
</button>
</div>
))}
</div>
</div>
);
};Available drawing modes from google.maps.drawing.OverlayType:
const drawingModes = {
MARKER: google.maps.drawing.OverlayType.MARKER,
CIRCLE: google.maps.drawing.OverlayType.CIRCLE,
POLYGON: google.maps.drawing.OverlayType.POLYGON,
POLYLINE: google.maps.drawing.OverlayType.POLYLINE,
RECTANGLE: google.maps.drawing.OverlayType.RECTANGLE
};Configure the appearance of each shape type:
const drawingOptions = {
drawingControl: true,
drawingControlOptions: {
position: google.maps.ControlPosition.TOP_CENTER,
drawingModes: [
google.maps.drawing.OverlayType.MARKER,
google.maps.drawing.OverlayType.CIRCLE,
google.maps.drawing.OverlayType.POLYGON,
google.maps.drawing.OverlayType.POLYLINE,
google.maps.drawing.OverlayType.RECTANGLE,
],
},
markerOptions: {
icon: {
url: 'https://maps.google.com/mapfiles/ms/icons/blue-dot.png',
scaledSize: { width: 32, height: 32 }
},
draggable: true
},
circleOptions: {
fillColor: '#ffff00',
fillOpacity: 0.3,
strokeColor: '#ffff00',
strokeWeight: 2,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
polygonOptions: {
fillColor: '#ff0000',
fillOpacity: 0.3,
strokeColor: '#ff0000',
strokeWeight: 2,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
polylineOptions: {
strokeColor: '#0000ff',
strokeWeight: 3,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
rectangleOptions: {
fillColor: '#00ff00',
fillOpacity: 0.3,
strokeColor: '#00ff00',
strokeWeight: 2,
clickable: true,
editable: true,
draggable: true,
zIndex: 1,
},
};Export drawn shapes as GeoJSON:
const exportShapesToGeoJSON = (shapes) => {
const features = shapes.map(shape => {
let geometry;
switch (shape.type) {
case google.maps.drawing.OverlayType.MARKER:
const position = shape.overlay.getPosition();
geometry = {
type: "Point",
coordinates: [position.lng(), position.lat()]
};
break;
case google.maps.drawing.OverlayType.POLYLINE:
const path = shape.overlay.getPath().getArray();
geometry = {
type: "LineString",
coordinates: path.map(point => [point.lng(), point.lat()])
};
break;
case google.maps.drawing.OverlayType.POLYGON:
const polygonPath = shape.overlay.getPath().getArray();
geometry = {
type: "Polygon",
coordinates: [polygonPath.map(point => [point.lng(), point.lat()])]
};
break;
case google.maps.drawing.OverlayType.CIRCLE:
const center = shape.overlay.getCenter();
const radius = shape.overlay.getRadius();
// Note: GeoJSON doesn't support circles directly
geometry = {
type: "Point",
coordinates: [center.lng(), center.lat()],
properties: { radius: radius }
};
break;
case google.maps.drawing.OverlayType.RECTANGLE:
const bounds = shape.overlay.getBounds();
const ne = bounds.getNorthEast();
const sw = bounds.getSouthWest();
geometry = {
type: "Polygon",
coordinates: [[
[sw.lng(), sw.lat()],
[ne.lng(), sw.lat()],
[ne.lng(), ne.lat()],
[sw.lng(), ne.lat()],
[sw.lng(), sw.lat()]
]]
};
break;
}
return {
type: "Feature",
properties: {
id: shape.id,
type: shape.type
},
geometry: geometry
};
});
return {
type: "FeatureCollection",
features: features
};
};Add validation rules for drawn shapes:
const ValidatedDrawingManager = () => {
const [shapes, setShapes] = useState([]);
const [errors, setErrors] = useState([]);
const validateShape = (shape, type) => {
const validation = { isValid: true, errors: [] };
switch (type) {
case google.maps.drawing.OverlayType.POLYGON:
const path = shape.getPath().getArray();
if (path.length < 3) {
validation.isValid = false;
validation.errors.push("Polygon must have at least 3 vertices");
}
// Check for minimum area
const area = google.maps.geometry.spherical.computeArea(path);
if (area < 1000) { // 1000 square meters
validation.isValid = false;
validation.errors.push("Polygon area must be at least 1000 sq meters");
}
break;
case google.maps.drawing.OverlayType.CIRCLE:
const radius = shape.getRadius();
if (radius < 50) { // 50 meters minimum
validation.isValid = false;
validation.errors.push("Circle radius must be at least 50 meters");
}
break;
case google.maps.drawing.OverlayType.POLYLINE:
const polylinePath = shape.getPath().getArray();
if (polylinePath.length < 2) {
validation.isValid = false;
validation.errors.push("Polyline must have at least 2 points");
}
break;
}
return validation;
};
const onOverlayComplete = (event) => {
const validation = validateShape(event.overlay, event.type);
if (validation.isValid) {
const newShape = {
id: Date.now(),
type: event.type,
overlay: event.overlay
};
setShapes(prevShapes => [...prevShapes, newShape]);
setErrors([]);
} else {
// Remove invalid shape from map
event.overlay.setMap(null);
setErrors(validation.errors);
}
};
return (
<div>
{errors.length > 0 && (
<div style={{
background: '#ffebee',
color: '#c62828',
padding: '10px',
margin: '10px 0'
}}>
<strong>Drawing Errors:</strong>
<ul>
{errors.map((error, index) => (
<li key={index}>{error}</li>
))}
</ul>
</div>
)}
<GoogleMap defaultZoom={10} defaultCenter={{ lat: 37.7749, lng: -122.4194 }}>
<DrawingManager
options={drawingOptions}
onOverlayComplete={onOverlayComplete}
/>
</GoogleMap>
</div>
);
};Enable snapping to existing shapes or grid:
const SnappingDrawingManager = () => {
const [snapToGrid, setSnapToGrid] = useState(true);
const gridSize = 0.001; // ~100m grid
const snapToGridPoint = (latLng) => {
if (!snapToGrid) return latLng;
const lat = Math.round(latLng.lat() / gridSize) * gridSize;
const lng = Math.round(latLng.lng() / gridSize) * gridSize;
return new google.maps.LatLng(lat, lng);
};
const onOverlayComplete = (event) => {
if (snapToGrid) {
switch (event.type) {
case google.maps.drawing.OverlayType.MARKER:
const snappedPosition = snapToGridPoint(event.overlay.getPosition());
event.overlay.setPosition(snappedPosition);
break;
case google.maps.drawing.OverlayType.POLYGON:
const path = event.overlay.getPath();
for (let i = 0; i < path.getLength(); i++) {
const snappedPoint = snapToGridPoint(path.getAt(i));
path.setAt(i, snappedPoint);
}
break;
}
}
// Continue with normal shape handling...
};
return (
<div>
<label>
<input
type="checkbox"
checked={snapToGrid}
onChange={(e) => setSnapToGrid(e.target.checked)}
/>
Snap to Grid
</label>
<GoogleMap defaultZoom={15} defaultCenter={{ lat: 37.7749, lng: -122.4194 }}>
<DrawingManager
options={drawingOptions}
onOverlayComplete={onOverlayComplete}
/>
</GoogleMap>
</div>
);
};Install with Tessl CLI
npx tessl i tessl/npm-react-google-maps