React Leaflet Routing Machine:onClick to add Marker after all the waypoints are removed仅在第二次单击后触发

Posted

技术标签:

【中文标题】React Leaflet Routing Machine:onClick to add Marker after all the waypoints are removed仅在第二次单击后触发【英文标题】:React Leaflet Routing Machine: onClick to add Marker after all waypoints are removed fires only after second click 【发布时间】:2021-01-25 18:50:33 【问题描述】:

我遵循了 Leaflet Routing Machine regarding interactions 的建议,即 onClicks。

通过我的实现,我将航路点保存在本地存储中——将我从地图点击中获得的纬度和经度 obj 保存到一个名为 markers 的数组中

事件处理程序有一个条件,它将点击分成两个结果——添加(到标记数组)或更新它。

就像我在标题中所说,初始交互很好,只是当我删除任何标记并尝试再次添加时才是问题所在。我还注意到markers 数组完全是空的,并且触发的下一个事件是一个更新,显然它应该是一个添加:

这是路由机中的相关代码:

class Routing extends MapLayer 
  static contextType = UserContextDispatch;

  constructor(props) 
    super(props);
    this.state = 
      showSpinner: false,
      localDispatch: null,
    ;

    this.handleLoader = this.handleLoader.bind(this);
    this.handleRemoveWayPoint = this.handleRemoveWayPoint.bind(this);
    this.handleSetMarker = this.handleSetMarker.bind(this);
  


  handleRemoveWayPoint() 

    var waypoints = this.control.getWaypoints();

    for (let i = waypoints.length - 1; i >= 0; i--) 
      console.log('waypoints[i].latLng !== null ', waypoints[i].latLng !== null);
      if (waypoints[i].latLng !== null) 
        waypoints[i].latLng = null;
        break;
      
    
    this.control.setWaypoints(waypoints);
  

  createLeafletElement(props) 
    const  map  = this.props.leaflet;

    if (map && !this.control) 

      this.control = L.Routing.control(
        collapsible: true,
        show: false,
        position: 'bottomleft',
        lineOptions: 
          styles: [ color: 'chartreuse', opacity: 1, weight: 5 ]
        ,
        waypoints: [null],
        createMarker: function(i, wp, nWps) 
          if (i === 0) 
            return L.marker(wp.latLng, 
              icon: startIcon,
              draggable: true,
              keyboard: true,
              alt: 'current location'
            ).on('drag', function(e) 
              e.latlng.alt = 'current location';

              console.log('there be dragons start!!', e);
              RoutingMachineRef.handleSetMarker(
                ...e.oldLatLng,
                ...e.latlng
              );
            );
          
          if (i === nWps - 1) 
            return L.marker(wp.latLng, 
              icon: endIcon,
              draggable: true,
              alt: 'current destination'
            ).on('drag', function(e) 
              e.latlng.alt = 'current destination';

              console.log('there be dragons dest!!', e);
              RoutingMachineRef.handleSetMarker(
                ...e.oldLatLng,
                ...e.latlng
              );
            );
          
        
      );

      L.Routing.errorControl(this.control).addTo(map);
    

    return this.control.getPlan();
  

  componentDidMount() 
    const  map  = this.props.leaflet;

    console.log('markers ', markers);
    this.setState(prevState => 
      localDispatch: prevState.localDispatch = this.context.dispatch;
    );

    map.addControl(this.control);
  

  updateLeafletElement(fromProps, toProps) 
    const  map  = this.props.leaflet;
    var self = this;
    self;
    var  markers  = this.props;

    function createButton(label, container) 
      var btn = L.DomUtil.create('button', '', container);
      btn.setAttribute('type', 'button');
      btn.innerhtml = label;
      return btn;
    

    var  localDispatch  = this.state;
    var container = L.DomUtil.create('div'),
      startBtn = createButton('Start from this location', container),
      destBtn = createButton('Go to this location', container);
    map.on(
      'click',

      function(e) 
        L.popup()
          .setContent(container)
          .setLatLng(e.latlng)
          .openOn(map);

        L.DomEvent.on(startBtn, 'click', function() 
  
          if (e.latlng) 
            e.latlng.alt = 'current location';
            console.log('adding);
            localDispatch(
              type: 'addMarker',
              payload: 
                marker: e.latlng
              
            );
          

          if (markers.length === 0) 
            console.log('updating ');
            e.latlng.alt = 'current location';

            localDispatch(
              type: 'updateMarkers',
              payload: 
                marker: e.latlng
              
            );
          

          self.control.spliceWaypoints(0, 1, e.latlng);
          map.closePopup();
        );

        L.DomEvent.on(
          destBtn,
          'click',
          function() 
            console.log('e', e);
            if (markers[1] === undefined) 
              e.latlng.alt = 'current destination';
              console.log('e.latlng ', e.latlng);
              localDispatch(
                type: 'addMarker',
                payload: 
                  marker: e.latlng
                
              );
            
            if (toProps.markers[1] !== undefined) 
              console.log('updating ');
              e.latlng.alt = 'current destination';

              localDispatch(
                type: 'updateMarkers',
                payload: 
                  marker: e.latlng
                
              );
            

            this.control.spliceWaypoints(1, 1, e.latlng);

            map.closePopup();
          .bind(this)
        );
      .bind(this)
    );


    if (toProps.removeRoutingMachine !== false) 
      this.control.setWaypoints([]);
    
  

  componentWillUnmount() 
    this.destroyRouting();
  

  destroyRouting() 
    const  map  = this.props.leaflet;
    if (map) 
      map.removeControl(this.control);
    
  


export default withLeaflet(Routing);

提前致谢!

【问题讨论】:

【参考方案1】:

如您所见,我在RoutingMachine 本身中有一些与Map(航点的onClick)相关的代码;经过考虑后,我将其作为handlerFunction 移至Map 组件。现在它可以工作了!

import React,  useState, useEffect, useRef  from 'react';
import  Button, Dimmer, Loader  from 'semantic-ui-react';

import L from 'leaflet';
import * as ELG from 'esri-leaflet-geocoder';

import Control from 'react-leaflet-control';
// import MapboxLayer from '../MapboxLayer/MapboxLayer.jsx';
import Routing from '../RoutingMachine/RoutingMachine.jsx';

import  parse, stringify  from 'flatted';

import  userState, userDispatch  from '../Context/UserContext.jsx';
import UIContext from '../Context/UIContext.jsx';

function currentMapViewPropsAreEqual(prevProps, nextProps) 
  console.log('prevProps, nextProps ', prevProps, nextProps);
  console.log(
    'prevProps.currentMapView === nextProps.currentMapView && prevProps.Map === nextProps.Map && prevProps.TileLayer === nextProps.TileLayer ',
    prevProps.currentMapView === nextProps.currentMapView &&
      prevProps.Map === nextProps.Map &&
      prevProps.TileLayer === nextProps.TileLayer
  );
  return (
    prevProps.currentMapView === nextProps.currentMapView &&
    prevProps.Map === nextProps.Map &&
    prevProps.TileLayer === nextProps.TileLayer
  );


function MyMap( currentMapView, Map, TileLayer ) 
  console.log('currentMapView; ', currentMapView);
  var [animate, setAnimate] = useState(false);
  var [userLocation, setUserLocation] = useState(null);

  const [myState, setMyState] = useState(null);

  var handleWaypointsOnMapRef = useRef(handleWaypointsOnMap);

  var mapRef = useRef();
  var mapRefForRoutingMachine = useRef();
  var  state  = userState();
  var  dispatch  = userDispatch();
  var 
    currentMap,
    isRoutingVisible,
    removeRoutingMachine,
    isLengthOfMarkersLessThanTwo,
    markers
   = state;

  useEffect(() => 
    handleWaypointsOnMapRef.current = handleWaypointsOnMap;
  ); // update after each render

  useEffect(() => 
    var  current =   = mapRef;
    var  leafletElement: map  = current;

    console.log('foo');

    console.log('currentMap ', currentMapView);
    map.locate( setView: true );
    map.on('locationfound', handleOnLocationFound);
  , []);

  useEffect(() => 
    var searchControl = new ELG.Geosearch(
      useMapBounds: false
    );
    var  current =   = mapRef;
    var  leafletElement: map  = current;

    console.log('mapRef ', mapRef);

    searchControl.addTo(map);

    var cb = e => handleWaypointsOnMapRef.current(e); // then use most recent cb value

    searchControl.on('results', cb);

    if (Object.keys(currentMap).length === 0) 
      dispatch(
        type: 'setMap',
        payload: 
          currentMap: stringify(map)
        
      );
    

    return () => 
      searchControl.off('results', cb);
    ;
  , []);

  function handleOnClickClearOneMarkerAtTime() 
    dispatch(
      type: 'setIsRoutingVisible',
      payload: 
        isRoutingVisible: false
      
    );
    mapRefForRoutingMachine.current.handleRemoveWayPoint();
    dispatch(
      type: 'deleteUserMarkers'
    );
  

  function handleOnClickClearAllMarkers() 
    mapRefForRoutingMachine.current.handleClearWayPoints();
    dispatch(
      type: 'resetUserMarkers'
    );
  

  function handleOnClickMarkerClick(e) 
    e.originalEvent.view.L.DomEvent.stopPropagation(e);
  

  function handleWaypointsOnMap(e) 
    var  current =   = mapRef;
    var  leafletElement: map  = current;
    dispatch(
      type: 'setIsRoutingVisible',
      payload: 
        isRoutingVisible: true
      
    );
    dispatch(
      type: 'setRemoveRoutingMachine',
      payload: 
        removeRoutingMachine: false
      
    );

    function createButton(label, container) 
      var btn = L.DomUtil.create('button', '', container);
      btn.setAttribute('type', 'button');
      btn.innerHTML = label;
      return btn;
    
    var container = L.DomUtil.create('div'),
      startBtn = createButton('Start from this location', container),
      destBtn = createButton('Go to this location', container);

    L.popup()
      .setContent(container)
      .setLatLng(e.latlng)
      .openOn(map);

    L.DomEvent.on(startBtn, 'click', function() 
      if (markers.length === 0) 
        e.latlng.alt = 'current location';

        console.log('adding current location', e.latlng);

        dispatch(
          type: 'addMarker',
          payload: 
            marker: e.latlng
          
        );
      

      if (markers[0] != undefined) 
        e.latlng.alt = 'current location';
        console.log('updating current location', e.latlng);

        dispatch(
          type: 'updateMarkers',
          payload: 
            marker: e.latlng
          
        );
      

      mapRefForRoutingMachine.current.handleSpliceWaypoints(0, 1, e.latlng);

      map.closePopup();
    );

    L.DomEvent.on(
      destBtn,
      'click',
      function() 
        console.log('e', e);
        if (markers.length === 1) 
          e.latlng.alt = 'current destination';
          console.log('adding destination ', e.latlng);
          dispatch(
            type: 'addMarker',
            payload: 
              marker: e.latlng
            
          );
        
        if (markers.length === 2 && markers[1] !== undefined) 
          e.latlng.alt = 'current destination';
          console.log('updating destination', e.latlng);

          dispatch(
            type: 'updateMarkers',
            payload: 
              marker: e.latlng
            
          );
        

        mapRefForRoutingMachine.current.handleSpliceWaypoints(1, 1, e.latlng);

        map.closePopup();
      .bind(this)
    );
  

  function handleOnViewportChanged(e) 
    console.log('viewport change', e);

    console.log('currentMapView ', currentMapView);
    var  current =   = mapRef;
    var  leafletElement: map  = current;

    map.on('zoomend', function() 
      var zoom = map.getZoom();
      console.log('zoom ', zoom);

      console.log("'dispatch setMapZoom'; ");
      dispatch(
        type: 'setMapZoom',
        payload: 
          currentMapView: zoom
        
      );
    );
  

  function handleOnLocationFound(e) 
    console.log('e ', e);
    var  current =   = mapRef;
    var  leafletElement: map  = current;
    map.setZoom(currentMapView);

    var latlng = e.latlng;
    var radius = e.accuracy;
    var circle = L.circle(latlng, radius);
    circle.addTo(map);
  

  return (
    <Map
      preferCanvas=true
      id="myMap"
      animate=animate
      zoom=currentMapView
      ref=mapRef
      onViewportChanged=handleOnViewportChanged
      onClick=e => handleWaypointsOnMap(e)
    >
      <TileLayer
        url=`https://api.mapbox.com/styles/v1/$process.env.MAPBOX_USERNAME/$
          process.env.MAPBOX_STYLE_ID
        /tiles/256/z/x/y@2x?access_token=$process.env.MAPBOX_ACCESS_TOKEN`
        attribution='Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery &copy; <a href="https://www.mapbox.com/">Mapbox</a>'
      />

      <Control position="bottomleft">
        <Button onClick=handleOnClickClearOneMarkerAtTime color="orange" size="small">
          delete one marker!
        </Button>
      </Control>
      <Control position="bottomright">
        <Button onClick=handleOnClickClearAllMarkers color="red" size="small">
          clear all!
        </Button>
      </Control>

      mapRef && (
        <Routing
          isRoutingVisible=isRoutingVisible
          ref=mapRefForRoutingMachine
          markers=markers
          stringify=stringify
          isLengthOfMarkersLessThanTwo=isLengthOfMarkersLessThanTwo
          removeRoutingMachine=removeRoutingMachine
          userLocation=userLocation
        />
      )
    </Map>
  );


var MemoizedMyMap = React.memo(MyMap, currentMapViewPropsAreEqual);

export default MemoizedMyMap;

这是路由机:

import  MapLayer  from 'react-leaflet';
import L from 'leaflet';
import 'leaflet-routing-machine';
import  withLeaflet  from 'react-leaflet';
import UserContextDispatch from '../Context/UserContext.jsx';
import  Dimmer, Loader  from 'semantic-ui-react';
import  isEqual  from 'lodash';

import AwesomeDebouncePromise from 'awesome-debounce-promise';

class Routing extends MapLayer 
  static contextType = UserContextDispatch;

  constructor(props) 
    super(props);
    this.state = 
      showSpinner: false,
      localDispatch: null,
      markerIsBeingDragged: false,
      currentMarker: 
    ;

    this.handleLoader = this.handleLoader.bind(this);
    this.handleRemoveWayPoint = this.handleRemoveWayPoint.bind(this);
    this.handleClearWayPoints = this.handleClearWayPoints.bind(this);
    this.handleSpliceWaypoints = this.handleSpliceWaypoints.bind(this);
    this.handleSetMarker = this.handleSetMarker.bind(this);
  

  handleSetMarker(marker) 
    var  markers  = this.props;

    if (markers[0] !== undefined && markers[0].alt === 'current location') 
      this.setState(prevState => (
        currentMarker:  ...prevState.currentMarker, ...marker 
      ));
    
    if (markers[1] !== undefined && markers[1].alt === 'current destination') 
      this.setState(prevState => (
        currentMarker:  ...prevState.currentMarker, ...marker 
      ));
    

    console.log('this.state ', this.state);
  

  handleSpliceWaypoints(start, end, obj) 
    this.control.spliceWaypoints(start, end, obj);
  

  handleLoader() 
    var  showSpinner  = this.state;

    if (this.state.showSpinner === false) 
      this.setState(function(prevState) 
        return  showSpinner: !prevState.showSpinner ;
      );
      return (
        <Dimmer active inverted>
          <Loader />
        </Dimmer>
      );
    
    this.setState(function(prevState) 
      return  showSpinner: (prevState.showSpinner = true) ;
    );
  

  handleRemoveWayPoint() 
    var waypoints = this.control.getWaypoints();

    for (let i = waypoints.length - 1; i >= 0; i--) 
      console.log('waypoints[i].latLng !== null ', waypoints[i].latLng !== null);
      if (waypoints[i].latLng !== null) 
        waypoints[i].latLng = null;
        break;
      
    
    console.log('waypoints ', waypoints);
    this.control.setWaypoints(waypoints);
  

  handleClearWayPoints() 
    this.control.setWaypoints([L.latLng(null, null), L.latLng(null, null)]);
  

  componentDidMount() 
    const  map  = this.props.leaflet;

    var  markers  = this.props;

    this.control.setWaypoints([L.latLng(markers[0]), L.latLng(markers[1])]);
    this.setState(prevState => 
      localDispatch: prevState.localDispatch = this.context.dispatch;
    );

    map.addControl(this.control);
  

  createLeafletElement(props) 
    const  map  = this.props.leaflet;

    var startIcon = new L.Icon(
      iconUrl:
        'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-green.png',
      shadowUrl:
        'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
      iconSize: [25, 41],
      iconAnchor: [12, 41],
      popupAnchor: [1, -34],
      shadowSize: [41, 41]
    );

    var endIcon = new L.Icon(
      iconUrl:
        'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
      shadowUrl:
        'https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.7/images/marker-shadow.png',
      iconSize: [25, 41],
      iconAnchor: [12, 41],
      popupAnchor: [1, -34],
      shadowSize: [41, 41]
    );

    if (map && !this.control) 
      var RoutingMachineRef = this;

      this.control = L.Routing.control(
        collapsible: true,
        show: false,
        position: 'bottomleft',
        lineOptions: 
          styles: [ color: 'chartreuse', opacity: 1, weight: 5 ]
        ,
        waypoints: [null],
        createMarker: function(i, wp, nWps) 
          if (i === 0) 
            return L.marker(wp.latLng, 
              icon: startIcon,
              draggable: true,
              keyboard: true,
              alt: 'current location'
            ).on('move', function(e) 
              e.target._latlng.alt = 'current location';
              console.log('e.target._latlng', e.target._latlng);

              console.log('there be dragons start!!', e);
              RoutingMachineRef.setState(prevState => (
                markerIsBeingDragged: !prevState.markerIsBeingDragged
              ));
              RoutingMachineRef.handleSetMarker(e.target._latlng);
            );
          
          if (i === nWps - 1) 
            return L.marker(wp.latLng, 
              icon: endIcon,
              draggable: true,
              alt: 'current destination'
            ).on('move', function(e) 
              e.target._latlng.alt = 'current destination';

              console.log(' e.target._latlng', e.target._latlng);
              RoutingMachineRef.setState(prevState => (
                markerIsBeingDragged: !prevState.markerIsBeingDragged
              ));
              console.log('there be dragons dest!!', e);
              RoutingMachineRef.handleSetMarker(e.target._latlng);
            );
          
        
      );

      L.Routing.errorControl(this.control).addTo(map);
    

    return this.control.getPlan();
  

  updateLeafletElement(fromProps, toProps) 
    var  currentMarker, localDispatch, markerIsBeingDragged  = this.state;

    // console.log('fromProps, toProps ', fromProps, toProps);
    if (markerIsBeingDragged && currentMarker.hasOwnProperty('alt')) 
      if (isEqual(toProps.markers[0], currentMarker) === false) 
        localDispatch(
          type: 'updateMarkers',
          payload: 
            marker: currentMarker
          
        );
      
      if (isEqual(toProps.markers[1], currentMarker) === false) 
        localDispatch(
          type: 'updateMarkers',
          payload: 
            marker: currentMarker
          
        );
      

      this.setState(prevState => (
        markerIsBeingDragged: !prevState.markerIsBeingDragged
      ));
    

    if (toProps.removeRoutingMachine !== false) 
      this.control.setWaypoints([]);
    
  

  componentWillUnmount() 
    console.log("'unmount' ", 'unmount');
    this.destroyRouting();
  

  destroyRouting() 
    const  map  = this.props.leaflet;
    if (map) 
      map.removeControl(this.control);
    
  


export default withLeaflet(Routing);

【讨论】:

以上是关于React Leaflet Routing Machine:onClick to add Marker after all the waypoints are removed仅在第二次单击后触发的主要内容,如果未能解决你的问题,请参考以下文章

在 react react-leaflet 中编译失败

安装 react-leaflet 版本 3 后 React Storybook 未运行

在react中使用leaflet

React-Leaflet在地图上绘制圆圈标记

在反应传单上使用Leaflet插件

使用 Leaflet-react 时如何调用 fitBounds()?