CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-google-maps

React.js Google Maps integration component library providing comprehensive Google Maps functionality through React components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

drawing.mddocs/

Drawing Tools

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.

Capabilities

DrawingManager

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>
  );
};

Drawing Manager Configuration

Drawing Modes

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
};

Shape Styling Options

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,
  },
};

Advanced Drawing Patterns

Shape Data Export

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
  };
};

Drawing with Validation

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>
  );
};

Drawing with Snapping

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

docs

addons.md

advanced.md

core-components.md

drawing.md

hocs.md

index.md

layers.md

places.md

shapes.md

visualization.md

tile.json