reactjsgoogle-mapsreact-google-maps-api

How to preload polygon in Google DrawingManager by passing predefined coordinates


I am using @react-google-maps/api to use google-maps in my react.js application, and I am using its different components. Now, I want to draw a polygon in Google DrawingManager using predefined polygons. How do I go about it?

import React, { useCallback, useRef, useState } from "react";
import { GoogleMap, useJsApiLoader } from "@react-google-maps/api";
import { DrawingManager } from "@react-google-maps/api";
export const libraries = ["geometry", "places", "drawing"];

function MyComponent({
  setPolygons,
  plottedPolygons,
  operationType,
  drawingEnabled,
  setDrawingEnabled,
}) {
  const { t } = useTranslation();
  const mapRef = useRef(null);
  const [zoom, setZoom] = useState(6);
  const [shape, setShape] = useState(null);
  const [center, setCenter] = useState({ lat: 23, lng: 54 });
 

  const { isLoaded } = useJsApiLoader({
    id: "script-loader",
    googleMapsApiKey: "key",
    language: "en",
    region: "us",
    libraries: libraries,
  });

  const onPolygonComplete = (polygon) => {
    // Handle completed polygon
    setDrawingEnabled(false);
    const paths = polygon
      .getPath()
      .getArray()
      .map((point) => ({
        lat: point.lat(),
        lng: point.lng(),
      }));

    // Make the polygon editable
    polygon.setOptions({
      draggable: false,
      editable: true,
    });

    // Listen for polygon edits
    window.google.maps.event.addListener(polygon.getPath(), "set_at", () => {
      handlePolygonEdit(polygon.getPath().getArray());
    });
    window.google.maps.event.addListener(polygon.getPath(), "insert_at", () => {
      handlePolygonEdit(polygon.getPath().getArray());
    });
    window.google.maps.event.addListener(polygon.getPath(), "remove_at", () => {
      handlePolygonEdit(polygon.getPath().getArray());
    });

    setPolygons(paths);
    //center map on polygon
    const bounds = new window.google.maps.LatLngBounds();
    polygon.getPath().forEach((element) => {
      bounds.extend(element);
    });
    mapRef.current.fitBounds(bounds);
    setZoom(mapRef.current.getZoom());
  };

  const handlePolygonEdit = (updatedPaths) => {
    const paths = updatedPaths.map((point) => ({
      lat: point.lat(),
      lng: point.lng(),
    }));
    console.log("Updated Polygon Paths:", paths);
    setPolygons(paths);
  };

  const onUnmount = useCallback(() => {
    mapRef.current = null;
  }, []);

  // const handleOnLoad = (map) => (mapRef.current = map);
  const handleOnLoad = (map) => {
    mapRef.current = map;

    // If in "edit" mode and there are plottedPolygons, fit bounds to show the polygon
    if (operationType === "edit" && plottedPolygons.length > 0) {
      const bounds = new window.google.maps.LatLngBounds();
      plottedPolygons.forEach((polygon) => {
        polygon.forEach((point) => {
          bounds.extend(point);
        });
      });
      mapRef.current.fitBounds(bounds);
    }
  };
  const drawingManagerRef = useRef(null);



  return (
    <>
      {isLoaded ? (
       
                  
                  <GoogleMap
                    key="google-map"
                    ref={mapRef}
                    mapContainerClassName="App-map"
                    zoom={zoom}
                    version="weekly"
                    mapContainerStyle={{
                      width: "100%",
                      height: "500px",
                    }}
                    options={{
                      gestureHandling: "greedy",
                      disableDefaultUI: true,
                      keyboardShortcuts: false,
                    }}
                    center={center}
                    onLoad={handleOnLoad}
                    onUnmount={onUnmount}
                  >
                    <DrawingManager
                      ref={drawingManagerRef}
                      onPolygonComplete={onPolygonComplete}
                      onOverlayComplete={(e) => {
                        setShape(e.overlay);
                      }}
                      options={{
                        drawingMode:
                          drawingEnabled && operationType === "add"
                            ? "polygon"
                            : null,

                        // drawingMode: drawingEnabled ? "polygon" : null,
                        drawingControl: drawingEnabled,
                        drawingControlOptions: {
                          position:
                            window.google.maps.ControlPosition.LEFT_CENTER,
                          drawingModes: ["polygon"],
                        },
                        markerOptions: {
                          draggable: false,
                        },

                        polylineOptions: {
                          editable: true,
                          draggable: false,
                        },
                        rectangleOptions: {
                          editable: true,
                          draggable: false,
                        },
                        circleOptions: {
                          editable: true,
                          draggable: false,
                        },

                        polygonOptions: {
                          editable: operationType === "edit", // Set to true only in edit mode
                          draggable: false,
                          fillOpacity: 0.5,
                          strokeWeight: 2,
                          strokeColor: "#0000FF",
                          zIndex: 999,
                          paths:
                            plottedPolygons.length > 0
                              ? plottedPolygons[0]
                              : [],
                        },
                      }}
                    />

                   
                  </GoogleMap>
               
      ) : (
        <div>loading</div>
      )}
    </>
  );
}

export default MyComponent

Solution

  • Use <Polygon/> component instead of the <DrawingManager/> for pre-loaded points

    The official API documentation is clear about the purpose of the DrawingManager class, which is to,

    "provide graphical interface for users to draw polygons, rectangles, polylines, circles, and markers on the map."

    The purpose of the DrawingManager is for the users to "manually" draw different kind of objects on the map. If you want to load a polygon with already existing data without the users having to draw them, the Google Maps API already had the Polygon objects.

    If your goal is to make it editable even after rendering, then you can just set the editable property of the PolylineOptions to true. As this will enable the user to edit the shape by dragging the control points shown at the vertices and on each segment. Ref: https://developers.google.com/maps/documentation/javascript/reference/polygon#PolylineOptions.editable

    In your case, since you are using the react-google-maps/api library, you can use the <Polyline/> component. The library supports the editable property so you should not have any issues implementing this.

    It should look something like this:

    import React from "react";
    import { GoogleMap, useJsApiLoader, Polyline } from "@react-google-maps/api";
    
    const containerStyle = {
      width: "100%",
      height: "100vh",
    };
    
    samplePolygon = [
      { lat: -3.746341, lng: -38.561108 },
      { lat: -3.742494, lng: -38.607967 },
      { lat: -3.81688, lng: -38.609995 },
      { lat: -3.815362, lng: -38.493357 },
      { lat: -3.746341, lng: -38.561108 },
    ];
    
    const center = {
      lat: -3.775,
      lng: -38.55,
    };
    
    const zoom = 12;
    
    function MyComponent() {
      const { isLoaded } = useJsApiLoader({
        id: "google-map-script",
        googleMapsApiKey: "API_KEY_HERE",
      });
    
      const [map, setMap] = React.useState(null);
    
      const onLoad = React.useCallback(function callback(map) {
        setMap(map);
      }, []);
    
      const onUnmount = React.useCallback(function callback(map) {
        setMap(null);
      }, []);
    
      return isLoaded ? (
        <GoogleMap
          mapContainerStyle={containerStyle}
          center={center}
          zoom={zoom}
          onLoad={onLoad}
          onUnmount={onUnmount}
        >
          <Polyline
            onLoad={() => {
              alert("Polyline Loaded");
            }}
            editable={true}
            path={samplePolygon}
          />
          <></>
        </GoogleMap>
      ) : (
        <></>
      );
    }
    
    export default React.memo(MyComponent);
    

    Here's a proof of concept codesandbox: Editable Polygon

    NOTE: Use your own API Key