0

I have 3 files:

1.

PolylineMeasure.jsx

import { MapControl, withLeaflet } from "react-leaflet";
import * as L from "leaflet";

class PolylineMeasure extends MapControl {
  createLeafletElement() {
    return L.control.polylineMeasure({
      position: "topleft",
      unit: "metres",
      showBearings: true,
      clearMeasurementsOnStop: false,
      showClearControl: true,
      showUnitControl: true,
    });
  }

  componentDidMount() {
    const { map } = this.props.leaflet;
    const polylineMeasure = this.leafletElement;
    polylineMeasure.addTo(map);
  }
}

export default withLeaflet(PolylineMeasure);

Map.jsx

import { Map, TileLayer } from "react-leaflet";
import PolylineMeasure from "./PolylineMeasure";

import "leaflet/dist/leaflet.css";
import "leaflet/dist/leaflet.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure.css";
import "leaflet.polylinemeasure/Leaflet.PolylineMeasure";

const Leaflet = () => {
  return (
    <>
      <Map
        center={[52.11, 19.21]}
        zoom={6}
        scrollWheelZoom={true}
        style={{ height: 600, width: "50%" }}
      >
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        <PolylineMeasure />
      </Map>
    </>
  );
};

export default Leaflet;
  1. I'm using nextjs so I had to import without SSR.

home.js

import dynamic from "next/dynamic";

function HomePage() {
  const Map = dynamic(() => import("../components/Map"), {
    loading: () => <p>A map is loading</p>,
    ssr: false,
  });
  return <Map />;
}

export default HomePage;

https://github.com/ppete2/Leaflet.PolylineMeasure

Using demos in link above, I was able to log an array of coorfinates like this:

{ ... }
polylineMeasure.addTo(map);
function debugevent() {
        polylineMeasure._arrPolylines[0].arrowMarkers.map((el) => {
          console.log(el._latlng);
        });
      }

      map.on("polylinemeasure:toggle", debugevent);

How can I access these coordinates in nextjs (home.js file)?

How to render PolylineMeasure (Map.jsx file) already with coordinates by passing down an array as props?

M_Balicki
  • 3
  • 2

2 Answers2

1

So this is about 2 things: lifting up state, and capturing Leaflet.Polyline's internal events.

First, let's keep track of a state variable in Home.js, and pass its setter down into the map component:

function HomePage() {
  const [pointarray, setPointarray] = useState()

  const Map = dynamic(() => import("../components/Map"), {...})
  return <Map setPointarray={setPointarray} />;
}

Now in Map, we need to get a reference to the underlying leaflet map so that we can attach some event handlers. You're using createLeafletElement and withLeaflet, so I assume you're using reat-leaflet version 2. (I recommend updating to v3 when you can).

const Leaflet = ({ setPointarray }) => {
  const mapRef = React.useRef()

  useEffect(() => {
    if (mapRef && mapRef.current){
      mapRef.current.leafletElement.on(
        'polylinemeasure:finish', 
        currentLine => setPointarray(currentLine.getLatLngs())
      )
    }
  }, [mapRef])

  return (
    <>
      <Map
        ref={mapRef}
        ...
      >
        <TileLayer ... />
        <PolylineMeasure />
      </Map>
    </>
  );
};

What happens here is that a ref is attached to your Map component, which references the underlying leaflet L.map instance. When that ref is ready, the code inside the useEffect if statement runs. It gets the map instance from mapRef.current.leafletElement, and attaches an event handler based on Leaflet.PolylineMeasure's events, specifically the event of when a drawing is complete. When that happens, it saves the drawn line to the state variable, which lives in the Home component.

There are a lot of variations on this, it just depends on what you're trying to do exactly. As far as feeding preexisting polyline coordinates down to PolylineMeasurer as props, I couldn't find any examples of that even with the vanilla leaflet PolylineMeasurer. I found a comment from the plugin author saying that "restoring of drawed measurements is not possible", which is essentially what we're talking about doing by passing props down to that component. I'm sure it can be done by digging into the source code and programmatically drawing a polyline, but I've run out of time, I'll try to revisit that later.

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78
  • This is great, thank you. I'm waiting for your follow up later then. I used react lefllet 2.7.0 because I didn't know how to replace `MapControl` as in here [link](https://stackoverflow.com/questions/64678752/attempted-import-error-mapcontrol-is-not-exported-from-react-leaflet) – M_Balicki Apr 14 '21 at 08:43
  • Yes, creating custom components for react-leaflet v3 is a whole different process. You can read about that in their docs, or [here](https://stackoverflow.com/questions/65663826/how-to-extend-tilelayer-component-in-react-leaflet-v3/65713838#65713838). Considering the author of leaflet polylinemeasurer said that what you want to do is "not possible", it would be going way above and beyond or me to dig into their source code and figure it out. I can't promise I'll have time for that. That being said, if this answered your question, please mark it as the answer. – Seth Lutske Apr 14 '21 at 14:13
  • Sure, but if you will find time especially for my second question I will appreciate that greatly. However there is no rush. – M_Balicki Apr 14 '21 at 14:26
  • Well it bothered me enough that I'm working on it. [Here is a CSB](https://codesandbox.io/s/polylinemeasure-from-points-bi80y) in vanilla leaflet that allows the author to seed a PolylineMeasurer with data. I'll come back to this later to implement it in react-leaflet – Seth Lutske Apr 14 '21 at 17:09
  • I've figured it out. I'd be happy to write up an answer, but I anticipate you having problems with rerenders if you're using react-leaflet version 2, especially if you want to allow the user to edit existing polylines. Are you able to upgrade to react-leaflet v3? – Seth Lutske Apr 14 '21 at 21:41
  • Awesome. Yes, I am able, no problem. – M_Balicki Apr 15 '21 at 06:52
  • Ok I put up a react-leaflet v3 answer – Seth Lutske Apr 15 '21 at 17:05
  • Sorry to bother you once again, but how should I replace `ref` for react-leaflet v3 and `MapContainer`? I saw a couple of solutions, but none seems to work for my case (i.e. [here](https://github.com/PaulLeCam/react-leaflet/issues/846)) – M_Balicki Apr 16 '21 at 11:43
  • If you need a reference to the underlying L.map instance, check out [this example](https://react-leaflet.js.org/docs/example-external-state) – Seth Lutske Apr 16 '21 at 14:14
1

react-leaflet version 3 answer

As per request, here's how to do this with react-leaflet v3, while initializing the polylinemeasurer with data passed down as props.

Create custom react-leaflet v3 control

Creating custom components with react-leaflet is easier than ever. Take a look at createcontrolcomponent. If you're not used to reading these docs, it boils down to this: to create a custom control component, you need to make a function that returns the leaflet instance of the control you want to make. You feed that function to createcontrolcomponent, and that's it:

import { createControlComponent } from "@react-leaflet/core";

const createPolylineMeasurer = (props) => {
  return L.control.polylineMeasure({ ...props });
};

const PolylineMeasurer = createControlComponent(createPolylineMeasurer);

export default PolylineMeasurer;

Altering the original plugin to seed data

However, in our case, we want to add some extra logic to pre-seed the PolylineMeasurer with some latlngs that we pass down as a prop. I put in a pull request to the original plugin to add a .seed method. However, in the case of react-leaflet, we need to be more careful than using the code I put there. A lot of the methods required to draw polylines are only available after the L.Control.PolylineMeasure has been added to the map. I spent probably way too much time trying to figure out where in the react/react-leaflet lifecyle to intercept the instance of the polylineMeasure after it had been added to the map, so my eventual solution was to alter the source code of Leaflet.PolylineMeasure.

In the onAdd method, after all the code has run, we add in this code, which says that if you use a seedData option, it will draw that seed data once the control is added to the map:

// inside L.Control.PolylineMeasure.onAdd:
onAdd: function(map) {

  // ... all original Leaflet.PolylineMeasure code here ...

  if (this.options.seedData) {
    const { seedData } = this.options;

    seedData.forEach((polyline) => {
      // toggle draw state on:
      this._toggleMeasure();
      // start line with first point of each polyline
      this._startLine(polyline[0]);
      // add subsequent points:
      polyline.forEach((point, ind) => {
        const latLng = L.latLng(point);
        this._mouseMove({ latLng });
        this._currentLine.addPoint(latLng);
        // on last point,
        if (ind === polyline.length - 1) {
          this._finishPolylinePath();
          this._toggleMeasure();
        }
      });
    });
  }

  return this._container;

}

This code programatically calls all the same events that would be called if a user turned on the control, clicked around, and drew their lines that way.

Tying it together

So now our <PolylineMeasurer /> component takes as its props the options that would be fed to L.control.polylineMeasure, in addition to a new optional prop called seedData which will cause the map to be rendered with that seedData:

const Map = () => {
  return (
    <MapContainer {...mapContainerProps}>
      <TileLayer url={url} />
      <PolylineMeasurer
        position="topleft"
        clearMeasurementsOnStop={false}
        seedData={seedData}
      />
    </MapContainer>
  );
};

Working Codesandbox

Caveat

If by some other mechanism in your app the seedData changes, you cannot expect the PolylineMeasurer component to react in the same way that normal React components do. In create leaflet, this control is added to the map once with the options you feed it, and that's it. While some react-leaflet-v3 component factory functions come with an update paramter, createcontrolcomponent does not (i.e. its first argument is a function which creates a control instance, but it does not accept a second argument to potentially update the control instance like, say, createlayercomponent does).

That being said, you can apply a key prop to the PolylineMeasurer component, and if your seedData is changed somewhere else in the app, also change the key, and the PolylineMeasurer will be forced to rerender and draw your new data.

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78