Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtain a segment of Polyline which was clicked

In my app I have:

<MapContainer {...opts} scrollWheelZoom>
  <>..</>
  <Polyline eventHandlers={{ click: e => { console.log('line clicked', e.sourceTarget) }, }} positions={positonArrat}/>}
</MapContainer>

Each polyline consists of N straight line sections.

How can I get the segment of polyline which was clicked without using rocket science?

Another option would be to replace a single polyline with mulitple lines, so that each click would get a single result, but a out-of-box solution would be nice...

like image 227
injecteer Avatar asked Oct 28 '25 15:10

injecteer


1 Answers

There are a few ways to do this, though nothing built-in / native to leaflet, as far as I know. Redrawing your polyline as a series of unisegmental polylines is one way, as you mentioned. You seem to have this plan of attack laid out in your mind already, so let's discuss another way.

This does not involve rocket science, but it does involve math. Your comment mentioned that you don't need to calculate distances as you already know that the point belongs to the polyline. Just knowing that the point belongs to the polyline is not enough. You need to know the shortest distance between the clicked point and each segment of the polyline. So you need distances.

leaflet-geometryutil offers some useful functions. The following code uses the distanceSegment function to get the shortest distance between a point and a unisegmental polyline. Let's look at the `onClick':

eventHandlers={{
  click: (e) => {
    const closest = getSegment(e.latlng, e.sourceTarget);
    setResults([...results, closest]);
  }
}}

On click, we pass the latlng and the L.polyline instance to getSegment. geSegment returns an object which contains the index of the L.polyline._latlngs array associated with the shortest distance to the clicked point, the distance, and for convenience, an array of that latlng and the one after it:

function getSegment(latlng, polyline) {

  // get layerpoint of user click
  const latlngs = polyline._latlngs;
  let segments = [];

  // get segments of polyline
  // calculate distances from point to each polyline
  for (let i = 0; i < latlngs.length - 1; i++) {
    const pointToLineDistance = GeoUtil.distanceSegment(
      polyline._map,
      latlng,
      latlngs[i],
      latlngs[i + 1]
    );

    segments.push({
      index: i,
      pointToLineDistance,
      segment: [latlngs[i], latlngs[i + 1]]
    });
  }

  // sort segments by shortest distance
  segments.sort((a, b) =>
    a.pointToLineDistance < b.pointToLineDistance ? -1 : 1
  );

  // return first entry, which has shortest distance
  return segments[0];
}

Since we're using react-leaflet, I used a useState function to capture any segments clicked, and then render them to the map:

{results.map((r, i) => (
  <Polyline key={`polyline-${i}`} positions={r.segment} color="green" />
))}

Working codesandbox

Click on the polyline in the sandbox. The closest segment to the click will be identified, saved to state, and rendered in green.

Other than your method of breaking the polyline up, or doing some math, I'm not sure how else this could be done. I'm sure you can come up with some variations

like image 155
Seth Lutske Avatar answered Oct 31 '25 06:10

Seth Lutske