无法访问 react-leaflet 中的 GeoJSON 层

Cannot access GeoJSON layer in react-leaflet

我正在使用 React Leaflet to render Leaflet map and its GeoJSON 组件来渲染多边形。我正在尝试将多个多边形作为一个组同时拖动到一起。

我添加了 Leaflet.Path.Drag library and tried to reuse this 代码。我能够获得处于父状态的转换矩阵。如果我想用 _transform 方法将这个矩阵应用于多个多边形,它不起作用。我认为原因是矩阵没有应用于正确的层,但我不知道如何解决这个问题。

codesandbox.io

App.js

import React from "react";
import { MapContainer, GeoJSON, TileLayer } from "react-leaflet";
import { geoJson, latLngBounds } from "leaflet";
import "./styles.css";
import "leaflet/dist/leaflet.css";

import { GeoJsonContainer } from "./GeoJsonContainer";

const objects = [
  {
    polygon: {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          properties: {},
          geometry: {
            type: "Polygon",
            coordinates: [
              [
                [-104.98569488525392, 39.63431579014969],
                [-104.98569488525392, 39.64165260123419],
                [-104.97161865234376, 39.64165260123419],
                [-104.97161865234376, 39.63431579014969]
              ]
            ]
          }
        }
      ]
    }
  },
  {
    polygon: {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          properties: {},
          geometry: {
            type: "Polygon",
            coordinates: [
              [
                [-105.02964019775392, 39.6206315500488],
                [-105.02964019775392, 39.65685252543906],
                [-104.99067306518556, 39.65685252543906],
                [-104.99067306518556, 39.6206315500488]
              ]
            ]
          }
        }
      ]
    }
  }
];

const getPolygonPointFromBounds = (latLngBounds) => {
  const center = latLngBounds.getCenter();
  const latlngs = [];

  latlngs.push(latLngBounds.getSouthWest()); //bottom left
  latlngs.push({ lat: latLngBounds.getSouth(), lng: center.lng }); //bottom center
  latlngs.push(latLngBounds.getSouthEast()); //bottom right
  latlngs.push({ lat: center.lat, lng: latLngBounds.getEast() }); // center right
  latlngs.push(latLngBounds.getNorthEast()); //top right
  latlngs.push({
    lat: latLngBounds.getNorth(),
    lng: latLngBounds.getCenter().lng
  }); //top center
  latlngs.push(latLngBounds.getNorthWest()); //top left
  latlngs.push({
    lat: latLngBounds.getCenter().lat,
    lng: latLngBounds.getWest()
  }); //center left

  return latlngs;
};

export default function App() {
  const [matrix, setMatrix] = React.useState(null);

  let newBounds = [];
  let selectBoundingBox = [];

  objects.forEach((building) => {
    const polygonBounds = geoJson(building.polygon).getBounds();
    newBounds = [...newBounds, polygonBounds];
  });

  const polygonPoints = getPolygonPointFromBounds(latLngBounds(newBounds));
  const convertedData = polygonPoints.map((point) => [point.lng, point.lat]);
  convertedData.push([polygonPoints[0].lng, polygonPoints[0].lat]);
  selectBoundingBox = convertedData;

  let selectBoxData = null;

  if (selectBoundingBox) {
    selectBoxData = {
      type: "FeatureCollection",
      features: [
        {
          type: "Feature",
          properties: {},
          geometry: {
            type: "Polygon",
            coordinates: [selectBoundingBox]
          }
        }
      ]
    };
  }

  const handleFeature = (layer) => {
    layer.makeDraggable();
    layer.dragging.enable();

    layer.on("drag", function (e) {
      setMatrix(layer.dragging._matrix);
    });
  };

  return (
    <MapContainer center={[39.63563779557324, -104.99234676361085]} zoom={12}>
      <TileLayer
        attribution='&amp;copy <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.osm.org/{z}/{x}/{y}.png"
      />
      {objects.map((object, i) => (
        <GeoJsonContainer data={object} key={i} matrix={matrix} />
      ))}
      <GeoJSON
        data={selectBoxData}
        style={() => ({
          color: "green",
          weight: 3,
          opacity: 0.5
        })}
        draggable={true}
        onEachFeature={(feature, layer) => handleFeature(layer)}
      ></GeoJSON>
    </MapContainer>
  );
}

GeoJsonContainer.js

import React from "react";
import { GeoJSON } from "react-leaflet";

require("leaflet-path-drag");

export const GeoJsonContainer = (props) => {
  const geoJSONRef = React.useRef(null);
  const layerRef = React.useRef(null);

  React.useEffect(() => {
    if (geoJSONRef?.current?._layers) {
      console.log("mount layers", geoJSONRef.current?._layers);
    }
  }, []);

  React.useEffect(() => {
    if (geoJSONRef?.current._layers && props.matrix) {
      console.log("transform layers", geoJSONRef.current._layers);
      const key = Object.keys(geoJSONRef.current._layers)[0];
      const geoJSONElementLayer = geoJSONRef.current._layers[key];
      if (geoJSONElementLayer) {
        console.log("geoJSONElementLayer", geoJSONElementLayer);
        console.log("layerRef.current", layerRef.current);
        geoJSONElementLayer._transform(props.matrix);
        layerRef.current._transform(props.matrix);
      }
    }
  }, [props.matrix]);

  const handleFeature = (layer) => {
    console.log("handleFeature layer", layer);
    layerRef.current = layer;
    layer.makeDraggable();
    layer.dragging.enable();
  };

  return (
    <GeoJSON
      ref={geoJSONRef}
      data={props.data.polygon}
      style={() => ({
        color: "#3388ff",
        weight: 3,
        opacity: 1
      })}
      dragging={true}
      onEachFeature={(feature, layer) => handleFeature(layer)}
    ></GeoJSON>
  );
};

关于

If I want to apply this matrix to multiple polygons with _transform method, it doesn't work

这是 React 中的预期行为,因为 matrix 道具需要 不可变 意味着每次发生变化时都需要传递一个新数组:

 layer.on("drag", function (e) {
   setMatrix([...layer.dragging._matrix]);
});

而不是:

layer.on("drag", function (e) {
   setMatrix(layer.dragging._matrix);
});

这样 GeoJsonContainer 组件应该会按预期重新呈现。

另外一个问题是Leaflet.Path.Drag插件,根据referenced thread,其实dropdropend事件都需要被捕获以正确应用转换,所以也许可以代替 matrix 道具,引入一个 transform 道具来保留矩阵数组和一个标志来确定 drop 还是 dropend 事件被触发:

const handleFeature = (layer) => {
    layer.makeDraggable();
    layer.dragging.enable();

    layer.on("drag", function (e) {
      setTransform({matrix: layer.dragging._matrix, "end": false});
    });

    layer.on("dragend", function (e) {
      setTransform({matrix: layer.dragging._matrix, "end": true});
    });
  };

并将其传递到 GeoJsonContainer 组件以应用几何变换:

React.useEffect(() => {

    if (props.transform) {
      geoJSONRef.current.eachLayer((layer) => {
        if (props.transform.end) dragDropTransform(layer);
        else __dragTransform(layer);
      });
    }
}, [props.transform]);

哪里

  function __dragTransform(layer) {
      layer._transform(props.transform.matrix);
   }

   function dragDropTransform(layer) {
     layer.dragging._transformPoints(props.transform.matrix);
     layer._updatePath();
     layer._project();
     layer._transform(null);
   }

Updated live demo

可能的改进:

  • 在提供的示例中 两个 个 JSON 层的实例被实例化,也许可以考虑创建 GeoJSON 的单个实例并为每个几何体应用一个样式?

解决方案改进建议

在提供的示例中实例化了两个层:

App组件

  • GeoJSON 渲染单个外部几何体(多边形)的图层

并且在 GeoJsonContainer 组件中另一个

  • GeoJSON 层依次呈现 两个 内部几何形状(多边形)

如何合并 两个 GeoJSON 对象,像这样:

const dataSource = {...selectBoxData,...objects[0],...objects[1]}    

并改为创建一个单层

<GeoJSON data={dataSource}></GeoJSON>

这样可以避免对可拖动层进行两次初始化(重构重复代码)