geoman-io / leaflet-geoman

🍂🗺️ The most powerful leaflet plugin for drawing and editing geometry layers
https://geoman.io
MIT License
2.21k stars 433 forks source link

problem: react-leaflet-geoman-v2 is not editing existing geojson comes from API. #1374

Closed Abbosbek-cloud closed 1 year ago

Abbosbek-cloud commented 1 year ago

Hello! I am using react-leaflet-geoman-v2 in my current project. It is working well when I create points polygons and lines! But there is a problem with the edition of recently drawn and saved shapes in DB.

After drawing the shape I am saving the features in the DB and get the data from the db when I want to edit the shapes. It is editing on the screen but, It does not change the values coming from the state!

This is my Geoman component to edit the shapes. It gets geojson value and draws the shapes in the map. But as I told above it does not change the values of geojson.

import * as React from "react";
import { FeatureGroup } from "react-leaflet";
import type { FeatureCollection } from "geojson";
import * as L from "leaflet";
import { GeomanControls } from "react-leaflet-geoman-v2";

export interface Props {
  geojson: FeatureCollection;
  cutPolygon: boolean;
  drawPolygon: boolean;
  setGeojson: (geojson: FeatureCollection) => void;
}

export default function Geoman({
  geojson,
  setGeojson,
  cutPolygon = false,
  drawPolygon = false,
}: Props) {
  const ref = React.useRef<L.FeatureGroup>(null);
  const [markerCreated, setMarkerCreated] = React.useState(false);

  const generateId = () => {
    let uniqueId = React.useId();
    return uniqueId;
  };

  React.useEffect(() => {
    if (ref.current?.getLayers().length === 0 && geojson) {
      L.geoJSON(geojson).eachLayer((layer) => {
        if (
          layer instanceof L.Polyline ||
          layer instanceof L.Polygon ||
          layer instanceof L.Marker
        ) {
          if (layer?.feature?.properties.radius && ref.current) {
            new L.Circle(layer.feature.geometry.coordinates.slice().reverse(), {
              radius: layer.feature?.properties.radius,
            }).addTo(ref.current);
          } else {
            ref.current?.addLayer(layer);
          }
        }
      });
    }
    if (!geojson.features.length) {
      setMarkerCreated(false);
    }
  }, [geojson, markerCreated, geojson.features.length]);

  const handleChange = () => {
    const newGeo: FeatureCollection = {
      type: "FeatureCollection",
      features: [],
    };
    const layers = ref.current?.getLayers();

    if (layers) {
      layers.forEach((layer) => {
        if (layer instanceof L.Circle || layer instanceof L.CircleMarker) {
          const { lat, lng } = layer.getLatLng();

          newGeo.features.push({
            type: "Feature",
            properties: {
              radius: layer.getRadius(),
            },
            geometry: {
              type: "Point",
              coordinates: [lng, lat],
            },
            id: generateId(),
          });
        } else if (
          layer instanceof L.Marker ||
          layer instanceof L.Polygon ||
          layer instanceof L.Rectangle ||
          layer instanceof L.Polyline
        ) {
          newGeo.features.push(layer.toGeoJSON());
        }
      });
    }
    if (!markerCreated && !geojson.features.length) {
      const markerLayer = ref.current
        ?.getLayers()
        .find((layer) => layer instanceof L.Marker);
      if (markerLayer) {
        setMarkerCreated(true);
      }
    }
    setGeojson(newGeo);
  };

  return (
    <FeatureGroup ref={ref}>
      <GeomanControls
        options={{
          position: "topleft",
          drawText: false,
          drawCircle: false,
          drawCircleMarker: false,
          drawPolyline: false,
          drawRectangle: false,
          drawPolygon,
          cutPolygon,
          drawMarker: !markerCreated,
          disableMarkerInsert: markerCreated,
        }}
        globalOptions={{
          continueDrawing: markerCreated,
          editable: true,
        }}
        eventDebugFn={console.log}
        onCreate={handleChange}
        onChange={handleChange}
        onUpdate={handleChange}
        onEdit={handleChange}
        onMapRemove={handleChange}
        onMapCut={handleChange}
        onDragEnd={handleChange}
        onMarkerDragEnd={handleChange}
      />
    </FeatureGroup>
  );
}

I checked the data coming from API, and it is right! You can see sample data below!

{
    "type": "Feature",
    "geometry": {
        "type": "Point",
        "coordinates": [
            65.810475,
            38.455542
        ]
    },
    "properties": {
        "name": "Something",
        "id": 1
    }
}
Falke-Design commented 1 year ago

Please try to create a demo with leaflet-geoman (vanilla js) or make an issue in TurtIeSocks/react-leaflet-geoman

Abbosbek-cloud commented 1 year ago

Hello, I solved the issue with react-leaflet-geoman. Here you can see the wrong code.

// @ts-nocheck
import { Breadcrumbs, Button, Grid, Stack, Typography } from "@mui/material";
import React from "react";
import CustomInputField from "../../components/inputs/CustomInputField";
import { useTranslation } from "react-i18next";
import GeneralScrollBar from "../../components/scrollbar";
import Lucide from "../../base-components/Lucide";
import { useFormik } from "formik";
import { LayersControl, MapContainer, TileLayer } from "react-leaflet";
import { CRS } from "leaflet";
import Geoman from "../../components/Geoman/GeomanPoint";
import type { FeatureCollection } from "geojson";
import { useDispatch } from "react-redux";
import {
  CreateFactoryThunk,
  GetOneFactoryThunk,
  UpdateFactoryThunk,
} from "../../stores/factorySlice";
import { useAppSelector } from "../../stores/hooks";
import { useNavigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";

export function BasicBreadcrumb({ name }: { name: string }) {
  const { t } = useTranslation();
  return (
    <Breadcrumbs aria-label="breadcrumb">
      <a color="inherit" href="/administration">
        {t("administration")}
      </a>
      <a color="inherit" href="/administration/ngdu">
        {t("ngdu")}
      </a>
      <Typography color="#1C3FAA">{name}</Typography>
    </Breadcrumbs>
  );
}

const NgduCreate = () => {
  const params = useParams();
  const navigate = useNavigate();
  const [status, setStatus] = React.useState("");

  function callback(data: any) {
    setTimeout(() => {
      navigate("/administration/ngdu");
    }, 2000);
    toast.success(data?.message, {
      position: "top-right",
      autoClose: 5000,
      hideProgressBar: false,
      closeOnClick: true,
      pauseOnHover: true,
      draggable: true,
      progress: undefined,
      theme: "light",
    });
  }

  const [geoJson, setGeoJsoN] = React.useState<FeatureCollection>(GEOJSON);

  const [position, setPosition] = React.useState<number[]>([
    38.458126079964785, 65.80552444969365,
  ]);
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const { auth } = useAppSelector((state) => state);
  const { values, handleChange, resetForm, handleSubmit, setFieldValue } =
    useFormik({
      initialValues: {
        description: "",
        name: "",
      },
      onSubmit: (values: any) => {
        const features = !!geoJson.features.length &&
          geoJson.features[0] && {
            ...geoJson.features[0],
          };
        features.id = { ...features, id: params?.id };
        Object.preventExtensions(features);
        const newValues = {
          name: values?.name,
          description: values.description,
          feature: features,
        };

        if (params.id) {
          let updatedValue = { ...newValues, id: params?.id, status };

          dispatch(
            UpdateFactoryThunk({
              data: updatedValue,
              token: auth.token,
              id: params.id,
              callback,
            })
          );
        } else {
          dispatch(
            CreateFactoryThunk({ data: newValues, token: auth.token, callback })
          );
        }
      },
    });

  const callBack = (data: any) => {
    console.log(data);
    setGeoJsoN({ ...geoJson, features: [data?.feature] });
    setPosition(data?.feature?.geometry?.coordinates);
    setFieldValue("name", data?.name);
    setFieldValue("description", data?.description);
    setStatus(data.status);
  };

  const getOneNgdu = React.useCallback(() => {
    dispatch(
      GetOneFactoryThunk({ id: params.id, token: auth.token, callBack })
    );
  }, [params?.id]);

  React.useEffect(() => {
    getOneNgdu();
  }, [getOneNgdu, position[0], position[1]]);

  return (
    <form onSubmit={handleSubmit}>
      <GeneralScrollBar>
        <Grid container spacing={2} className="relative h-full">
          <Grid item xs={12} sm={12} md={6}>
            <BasicBreadcrumb
              name={params.id ? values?.name : t("ngdu-create")}
            />
            <div className="flex flex-col justify-start gap-10 h-full">
              <CustomInputField
                name="name"
                onChange={handleChange}
                value={values?.name}
                placeholder={t("enter-name")}
                size="small"
                fullWidth
                label={t("name")}
                InputProps={{
                  style: {
                    height: "auto",
                    backgroundColor: "#ffff",
                  },
                }}
              />
              <CustomInputField
                fullWidth
                label={t("description")}
                rows={10}
                multiline
                name="description"
                value={values.description}
                InputProps={{
                  style: {
                    height: "auto",
                    backgroundColor: "#ffff",
                  },
                }}
                onChange={handleChange}
              />
            </div>
          </Grid>
          <Grid item xs={12} sm={12} md={6}>
            <MapContainer
              style={{ height: `calc(100vh - 200px)` }}
              crs={CRS.EPSG3857}
              className="map"
              zoom={12}
              center={{ lat: position[0], lng: position[1] }}
              scrollWheelZoom={true}
            >
              <LayersControl>
                <LayersControl.BaseLayer name="Open Street Map" checked>
                  <TileLayer
                    attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
                    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
                  />
                </LayersControl.BaseLayer>

                <LayersControl.BaseLayer name="Sattelite">
                  <TileLayer
                    url="https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
                    maxZoom={20}
                    subdomains={["mt1", "mt2", "mt3"]}
                  />
                </LayersControl.BaseLayer>
              </LayersControl>
                <Geoman
                  geojson={geoJson}
                  setGeojson={setGeoJsoN}
                  cutPolygon={false}
                  drawPolygon={false}
                />
            </MapContainer>
          </Grid>
          <Stack className="absolute bottom-0 right-0">
            <div className="flex gap-3">
              <Button
                variant="outlined"
                sx={{ borderColor: "#1C3FAA", color: "#1C3FAA" }}
              >
                {t("cancel")}
              </Button>
              <Button
                type="submit"
                variant="contained"
                sx={{ color: "#ffff", backgroundColor: "#1C3FAA" }}
              >
                <Lucide icon="Save" />
                <span className="ml-1">{t("save")}</span>
              </Button>
            </div>
          </Stack>
        </Grid>
      </GeneralScrollBar>
    </form>
  );
};

export default NgduCreate;

export const GEOJSON: FeatureCollection = {
  type: "FeatureCollection",
  features: [],
};

I solved the issue by checking to show the Geoman controller if features exist or not!

{geoJson.features.length && (
    <Geoman
       geojson={geoJson}
       setGeojson={setGeoJsoN}
       cutPolygon={false}
       drawPolygon={false}
    />
)}