Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
504 views
in Technique[技术] by (71.8m points)

reactjs - React Leaflet Routing Machine: onClick to add Marker after all waypoints are removed fires only after second click

I followed this recommendation from the Leaflet Routing Machine regarding interactions i.e. onClicks.

With my implementation, I'm saving the waypoints in local-storage—saving the latitude and longitude obj I get from the map click, to an array called markers

The event handler has a conditional which separates the click into two outcomes—an adding (to the markers array) or updating it.

Like I said in the title, initial interaction is fine, it's just when I remove any marker and try to add it again is the problem. Also I noticed the markers array is completely empty, and next event fired is an update when clearly it should be an addition:

Here is the relevant code in the Routing Machine:

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);

Thanks in advance!

question from:https://stackoverflow.com/questions/65890679/react-leaflet-routing-machine-onclick-to-add-marker-after-all-waypoints-are-rem

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

As you can see I have some code related to the Map (the onClick for the waypoints) in the RoutingMachine itself; after thinking about it I moved it to the Map component as a handlerFunction. And now it works!

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;

And this is the Routing Machine:

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);
  }

  createLeafletElemen

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...