You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Leaflet中基于手动标记点的RoutingMachine路径规划方案求助

React-Leaflet + RoutingMachine 标记点路径生成解决方案

先说说原代码里的几个核心问题:

  • 直接修改props里的数组(props.geoPoints.push这类操作):React里props是只读的,必须通过父组件传的回调函数更新数据
  • 本地的geoPoints用数组push直接修改,没调用setGeoPoints,导致组件不重新渲染,路径也不会更新
  • RoutingMachine的更新逻辑没和标记点变化绑定,加了新标记也不会生成新路径
  • routeselected事件可能重复绑定,触发多次无效回调
  • 直接用原生Leaflet API添加标记,没用到React-Leaflet的组件化特性,状态不好统一管理

下面是修复后的完整代码:

import React, { useEffect, useState, useRef } from "react";
import L from "leaflet";

import {
  TileLayer,
  MapContainer,
  LayersControl,
  Marker,
  useMapEvent,
} from "react-leaflet";
import { Button } from "@material-ui/core";
import "leaflet-routing-machine";
import "leaflet-routing-machine/dist/leaflet-routing-machine.css";

import osm from "./osm-providers";
import AuthService from "../../../services/auth.service";

import markerICon from "../../../assets/Default/map_blue.svg";

const AddRouteFenceMap = (props) => {
  const [map, setMap] = useState(null);
  // 本地存储RoutingMachine需要的坐标点
  const [localGeoPoints, setLocalGeoPoints] = useState([]);
  // 存储要渲染的标记点数据,给React-Leaflet的Marker组件使用
  const [markerList, setMarkerList] = useState([]);
  const routingControlRef = useRef(null);

  const icon = L.icon({
    iconSize: [25, 41],
    iconAnchor: [10, 41],
    popupAnchor: [2, -40],
    iconUrl: markerICon,
  });

  // 初始化RoutingMachine控件
  useEffect(() => {
    if (!map) return;

    // 创建路由控件实例
    const routingControl = L.Routing.control({
      position: "bottomleft",
      lineOptions: {
        styles: [{ color: "#757de8", weight: 4 }],
      },
      waypoints: [],
      // 关闭默认的添加/删除控制点,只使用手动添加的标记
      addWaypoints: false,
    });

    // 监听路由选择事件,将路径坐标传递给父组件
    routingControl.on("routeselected", (e) => {
      const routeCoordinates = e.route.coordinates.map(coord => ({
        latitude: coord.lat,
        longitude: coord.lng,
        address: "",
        isAuto: 1,
      }));
      // 调用父组件的回调更新数据
      if (props.onRouteCoordinatesChange) {
        props.onRouteCoordinatesChange(routeCoordinates);
      }
    });

    routingControlRef.current = routingControl;
    routingControl.addTo(map);

    // 组件卸载时清理路由控件,避免内存泄漏
    return () => {
      if (routingControlRef.current) {
        map.removeControl(routingControlRef.current);
      }
    };
  }, [map]);

  // 本地标记点变化时,自动更新RoutingMachine的路径
  useEffect(() => {
    if (!routingControlRef.current || localGeoPoints.length < 2) return;

    // 将坐标转换为Leaflet需要的LatLng对象
    const waypoints = localGeoPoints.map(point => L.latLng(point.lat, point.lng));
    routingControlRef.current.setWaypoints(waypoints);
  }, [localGeoPoints]);

  // 处理地图点击添加标记的逻辑
  const MapClickHandler = () => {
    useMapEvent("click", async (e) => {
      const { lat, lng } = e.latlng;
      let address = "";

      // 调用接口获取地址,增加错误处理
      try {
        address = await AuthService.getAddressFromGeoAPI(lat, lng);
      } catch (err) {
        console.error("获取地址失败:", err);
      }

      // 更新本地标记点数据
      const newMarker = { lat, lng, address };
      setMarkerList(prev => [...prev, newMarker]);
      setLocalGeoPoints(prev => [...prev, { lat, lng }]);

      // 通知父组件更新数据
      if (props.onWaypointsChange) {
        props.onWaypointsChange([
          ...props.waypoints,
          { location: { lat, lng }, stopover: true }
        ]);
      }
      if (props.onGeoPointsChange) {
        props.onGeoPointsChange([
          ...props.geoPoints,
          { latitude: lat, longitude: lng, address, isAuto: 0 }
        ]);
      }
    });
    return null;
  };

  return (
    <MapContainer
      center={props.center}
      zoom={12}
      zoomControl={true}
      style={{ height: "100vh", width: "100%", padding: 0 }}
      whenCreated={(mapInstance) => setMap(mapInstance)}
    >
      <LayersControl position="topright">
        <LayersControl.BaseLayer checked name="Map">
          <TileLayer
            url={osm.maptiler.url}
            attribution={osm.maptiler.attribution}
          />
        </LayersControl.BaseLayer>
      </LayersControl>

      {/* 地图点击事件处理器 */}
      <MapClickHandler />

      {/* 渲染所有手动添加的标记点 */}
      {markerList.map((marker, index) => (
        <Marker
          key={`marker-${index}`}
          position={[marker.lat, marker.lng]}
          icon={icon}
        />
      ))}
    </MapContainer>
  );
};

export default AddRouteFenceMap;

主要改了这些地方:

  1. props更新方式修正:
    • 不再直接修改props数组,而是让父组件传递onGeoPointsChange、onWaypointsChange、onRouteCoordinatesChange回调函数,通过回调更新父组件状态
  2. 本地状态管理优化:
    • 用localGeoPoints存储RoutingMachine需要的坐标,markerList存储标记点渲染数据,均通过setState更新,保证组件能触发重渲染
  3. RoutingMachine逻辑优化:
    • 初始化时绑定一次routeselected事件,组件卸载时清理控件,避免内存泄漏
    • 当localGeoPoints变化(添加新标记),自动更新RoutingMachine的路径点
    • 关闭默认的添加/删除控制点,确保路径仅由手动添加的标记生成
  4. 标记点渲染优化:
    • 使用React-Leaflet的Marker组件渲染标记,不再用原生Leaflet API,统一由React状态管理
  5. 异步逻辑处理:
    • 将获取地址的异步逻辑整合到点击事件中,增加try/catch处理错误,避免接口报错中断流程

父组件使用示例

// 父组件示例
const ParentComponent = () => {
  const [waypoints, setWaypoints] = useState([]);
  const [geoPoints, setGeoPoints] = useState([]);
  const [routeCoordinates, setRouteCoordinates] = useState([]);

  return (
    <AddRouteFenceMap
      center={[39.9042, 116.4074]} // 替换为你的默认地图中心点
      waypoints={waypoints}
      geoPoints={geoPoints}
      onWaypointsChange={setWaypoints}
      onGeoPointsChange={setGeoPoints}
      onRouteCoordinatesChange={setRouteCoordinates}
    />
  );
};

内容的提问来源于stack exchange,提问作者akshay kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:56:02