Geographic Information Systems Asked by AustrianTrails on April 7, 2021
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
}
};
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.
Answered by Seth Lutske on April 7, 2021
Get help from others!
Recent Answers
Recent Questions
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP