1

Is it possible to interpolate points between multiple points on a drawn line in leaflet? I am creating a site where users can draw running routes and see an elevation chart, but currently only where the users clicks is processed - this makes a sharp looking elevation chart. I was looking to interpolate between points so the elevation chart has more points to work with and therefore looks smoother.

I have been looking at Leaflet.GeometryUtil and interpolateOnLine, but it is a bit above me and after a few failed attempts - I'm not sure how to go about it.

Where the drawing takes place:

map.on(L.Draw.Event.CREATED, function(event) {
    var layer = event.layer;
    console.log("layer", layer.getLatLngs());
    
    
    drawnItems.addLayer(layer);
    drawData = layer.toGeoJSON();

    var profile = drawData.geometry.coordinates;

    //reverse coordinates
        function reverseCoordinates(_arr){
            let i;
            let number = []
            let xx
            for(i=0;i<_arr.length;i++){
                xx = _arr[i].reverse()
                number.push(xx)
            }
            return number;
        }
        fetch('https://open.mapquestapi.com/elevation/v1/profile?key=tHXSNAGXRx6LAoBNdgjjLycOhGqJalg7&shapeFormat=raw&latLngCollection='+reverseCoordinates(profile))
          .then(r => r.json()) 
          .then(data => {
            var latlngs = [];
                for (var i=0; i<data.shapePoints.length; i=i+2) {
                    //console.log(data.shapePoints[i])
                    latlngs.push([data.shapePoints[i+1], data.shapePoints[i], data.elevationProfile[i/2].height]);
                }

            geojson = {
                "type": "Feature",
                "geometry": {
                    "type": "LineString",
                    "coordinates": latlngs
                }
            };
3
  • 1
    It's not clear from your question if you want to interpolate line points on the map, only the points in elevation profile or just the way elevation profile is drawn to be more smooth? It's also not clear from your code how elevation profile is displayed. There seems to be only calculation of min and max height, and even these values are not used anywhere.
    – TomazicM
    Commented Aug 30, 2020 at 11:02
  • Thanks for replying @TomazicM, after a crash course in Javascript over the last few days I can now see how my code wasn't helpful. I have updated it now to better reflect my question. I am looking to interpolate points on the drawn line so that a smoother chart is drawn as a result. I have been trying to interpolate points on the line so that a line with more points is passed to mapquest, which in turn leads to a smoother elevation chart since more elevation data is returned. I successfully returned more points with L.geometryUtil.interpolateOnLine, but not sure where to go from there. Commented Aug 31, 2020 at 23:12
  • Please don't put additional explanations of the question in the comments. Edit the question and include them there.
    – TomazicM
    Commented Sep 1, 2020 at 10:57

1 Answer 1

1

GeometryUtil is a good option. Right away in your .CREATED event, you grab the latlngs. That is where you want to use interpolateOnLine. interpolateOnLine will get interpolated points for you, but you need to use the .predecessor value of the result to know how to inject that interpolated point back into your original array of latlngs. I found myself in this situation before, and I came up with this function:

/**
 * Calculate size of each segment and inject a midpoint when a segment grows too large
 * @param {array} latlngArray
 * @param {number} maxSegmentSize - segments larger than this will be interpolated
 */
export function interpolateLargeSegments(latlngArray, maxSegmentSize){

   const interpolatedLatLngs = [...latlngArray]

   var addedPoints = []

   // get interpolated points, push to arr addedPoints
   for (let i = 0; i < interpolatedLatLngs.length; i++){

      var nextPointIndex = i === interpolatedLatLngs.length - 1 ? 0 : i + 1
      var currentPoint = interpolatedLatLngs[i]
      var nextPoint = interpolatedLatLngs[nextPointIndex]
      var distanceToNext = map.distance(currentPoint, nextPoint)

      if (distanceToNext > maxSegmentSize) {
         var addedPoint = {
            index: i,
            point: L.GeometryUtil.interpolateOnLine(map, [currentPoint, nextPoint], 0.5).latLng
         }
         addedPoints.push(addedPoint)
      }

   }

   // loop through arr addedPoints, fold points into array at appropriate place 
   // (their own index + the index of their preceeding point + 1)
   for (let i = 0; i < addedPoints.length; i++){
      interpolatedLatLngs.splice( addedPoints[i].index + i + 1, 0, addedPoints[i].point)
   }

   return interpolatedLatLngs

}

This may be more technical than you were looking for, but feel free to treat this function as a black box if you want. It takes your array of leaflet latlngs, looks for segments that are smaller than the maxSegmentSize, and interpolates them, placing a point at 50% of the distance between those points. I found this helpful because you may not want certain segments to be interpolated if your user is drawing them close together already, or if they're drawing a polyline with 100 points and your height profile already has good resolution. If the resultant latlngs array still has points that are farther apart than you'd like, just call this function again on the result of what it returned the first time. You can probably put it on a loop that hits a stop when none of the segments are larger than the maxSegmentSize. There are other ways you can modify this to suit your needs.

You can use this function (or some version of it) right at the start of your .CREATED event. You would use this on your layer's latlngs:

// 
map.on(L.Draw.Event.CREATED, function(event) {

  const layer = event.layer;
  const latlngs = layer.getLatlngs()

  const interpolatedLatLngs = interpolateLargeSegments(latlngs, someThreshholdValue)

Now you need to make a choice, the choice that TomazicM asked you about in his comment. Do you want to use these new interpolated latlngs to draw your path on the map, or do you want to use the original drawn values for rendering the line, but use the interpolatedLatLngs to create your height profile? In the event that you want to use the new interpolatedLatLngs to both draw your path and get your heights, you can do this:

// still in map.on(L.Draw.Event.CREATED, () => { ... })

  const interpolatedPolyline = L.polyline(interpolatedLatLngs, options)
  drawnItems.removeLayer(layer) // remove original drawn layer
  drawnItems.addLayer(interpolatedPolyline); // add new interpolated layer
  drawData = interpolatedPolyline.toGeoJSON();

  var profile = drawData.geometry.coordinates;

  ...

If you want to leave the drawn path as the user drew it, but use the interpolatedLatLngs for your height profile, leave the original layer alone:

// still in map.on(L.Draw.Event.CREATED, () => { ... })

  // leave your original layer
  drawnItems.addLayer(layer);

  // but create your GeoJSON from the interpolated latlng array
  const interpolatedPolyline = L.polyline(interpolatedLatLngs, options)
  drawData = interpolatedPolyline.toGeoJSON();

  ...

This is how I would approach this problem. As I'm not familiar with leaflet Draw, I can't make a working example, but if you want to create a working example in a codesandbox / codepen, I could try to implement this (or another) solution in real code if you like.

Side note: I'm finding it very strange that you're converting your polyline into GeoJSON, and then using a reverseCoordinates function on the geometry feature of that GeoJSON. Why? You're essentially ending up with your latlngs array again. Why bother converting to GeoJSON and back again? Food for thought.

1
  • Thanks for the insightful post Seth! I was able to grab the latlngs, but your solution really helped clarify how to put it all together. I have been playing around with your code and have been able to use it to return an interpolated line, which in turn, makes a much better-looking elevation chart. As for the reverseCoordinates function (bit messy), as far as I understand it Leaflet uses latlng and GeoJSON uses lnglat, without the function my line shows in Saudi Arabi instead of Austria. If you're still happy to look at a Codepen, I will get something together for you to look at. Commented Sep 3, 2020 at 19:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.