无法访问 react-leaflet

问题描述

我使用 React Leaflet 来渲染 Leaflet 地图及其 GeoJSON 组件来渲染多边形。我正在尝试将多个多边形作为一个组一起拖动。

添加Leaflet.Path.Drag 库并尝试重用 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.63431579014969]
              ]
            ]
          }
        }
      ]
    }
  },{
    polygon: {
      type: "FeatureCollection",coordinates: [
              [
                [-105.02964019775392,39.6206315500488],[-105.02964019775392,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",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",opacity: 1
      })}
      dragging={true}
      onEachFeature={(feature,layer) => handleFeature(layer)}
    ></GeoJSON>
  );
};

解决方法

关于

如果我想使用 _transform 将此矩阵应用于多个多边形 方法,它不起作用

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

 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 道具来保持矩阵数组和一个标志来确定是否触发了 dropdropend 事件:>

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

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

    layer.on("dragend","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>

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