Skip to content

Instantly share code, notes, and snippets.

@twelch
Last active October 1, 2016 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save twelch/1ef68c532639f6d3a23e to your computer and use it in GitHub Desktop.
Save twelch/1ef68c532639f6d3a23e to your computer and use it in GitHub Desktop.
Interpolate points along a line
license: mit

Interpolate points along a line in equal segments in one pass, rather than say using turf-along and calling once to generate each point. Once generated it is straightforward to animate a point along this path.

Interpolate function borrowed from https://github.com/pelias/line-interpolate-points all rights reserved

Built with blockbuilder.org

<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.12.0/mapbox-gl.js'></script>
<script src='https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.2/turf.min.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.12.0/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
</head>
<body>
<div id='map'></div>
<script>
/* Interpolate function borrowed from https://github.com/pelias/line-interpolate-points all rights reserved */
/**
* @param {Point} pt1
* @param {Point} pt1
* @return number The Euclidean distance between `pt1` and `pt2`.
*/
function distance( pt1, pt2 ){
var deltaX = pt1[0] - pt2[0];
var deltaY = pt1[1] - pt2[1];
return Math.sqrt( deltaX * deltaX + deltaY * deltaY );
}
/**
* @param {Point} point The Point object to offset.
* @param {number} dx The delta-x of the line segment from which `point` will
* be offset.
* @param {number} dy The delta-y of the line segment from which `point` will
* be offset.
* @param {number} distRatio The quotient of the distance to offset `point`
* by and the distance of the line segment from which it is being offset.
*/
function offsetPoint( point, dx, dy, distRatio ){
return [
point[ 0 ] - dy * distRatio,
point[ 1 ] + dx * distRatio
];
}
/**
* @param {array of Point} ctrlPoints The vertices of the (multi-segment) line
* to be interpolate along.
* @param {int} number The number of points to interpolate along the line; this
* includes the endpoints, and has an effective minimum value of 2 (if a
* smaller number is given, then the endpoints will still be returned).
* @param {number} [offsetDist] An optional perpendicular distance to offset
* each point from the line-segment it would otherwise lie on.
* @param {int} [minGap] An optional minimum gap to maintain between subsequent
* interpolated points; if the projected gap between subsequent points for
* a set of `number` points is lower than this value, `number` will be
* decreased to a suitable value.
*/
function interpolateLineRange( ctrlPoints, number, offsetDist, minGap ){
minGap = minGap || 0;
offsetDist = offsetDist || 0;
// Calculate path distance from each control point (vertex) to the beginning
// of the line, and also the ratio of `offsetDist` to the length of every
// line segment, for use in computing offsets.
var totalDist = 0;
var ctrlPtDists = [ 0 ];
var ptOffsetRatios = [];
for( var pt = 1; pt < ctrlPoints.length; pt++ ){
var dist = distance( ctrlPoints[ pt ], ctrlPoints[ pt - 1 ] );
totalDist += dist;
ptOffsetRatios.push( offsetDist / dist );
ctrlPtDists.push( totalDist );
}
if( totalDist / (number - 1) < minGap ){
number = totalDist / minGap + 1;
}
// Variables used to control interpolation.
var step = totalDist / (number - 1);
var interpPoints = [ offsetPoint(
ctrlPoints[ 0 ],
ctrlPoints[ 1 ][ 0 ] - ctrlPoints[ 0 ][ 0 ],
ctrlPoints[ 1 ][ 1 ] - ctrlPoints[ 0 ][ 1 ],
ptOffsetRatios[ 0 ]
)];
var prevCtrlPtInd = 0;
var currDist = 0;
var currPoint = ctrlPoints[ 0 ];
var nextDist = step;
for( pt = 0; pt < number - 2; pt++ ){
// Find the segment in which the next interpolated point lies.
while( nextDist > ctrlPtDists[ prevCtrlPtInd + 1 ] ){
prevCtrlPtInd++;
currDist = ctrlPtDists[ prevCtrlPtInd ];
currPoint = ctrlPoints[ prevCtrlPtInd ];
}
// Interpolate the coordinates of the next point along the current segment.
var remainingDist = nextDist - currDist;
var ctrlPtsDeltaX = ctrlPoints[ prevCtrlPtInd + 1 ][ 0 ] -
ctrlPoints[ prevCtrlPtInd ][ 0 ];
var ctrlPtsDeltaY = ctrlPoints[ prevCtrlPtInd + 1 ][ 1 ] -
ctrlPoints[ prevCtrlPtInd ][ 1 ];
var ctrlPtsDist = ctrlPtDists[ prevCtrlPtInd + 1 ] -
ctrlPtDists[ prevCtrlPtInd ];
var distRatio = remainingDist / ctrlPtsDist;
currPoint = [
currPoint[ 0 ] + ctrlPtsDeltaX * distRatio,
currPoint[ 1 ] + ctrlPtsDeltaY * distRatio
];
// Offset currPoint according to `offsetDist`.
var offsetRatio = offsetDist / ctrlPtsDist;
interpPoints.push( offsetPoint(
currPoint, ctrlPtsDeltaX, ctrlPtsDeltaY, ptOffsetRatios[ prevCtrlPtInd ])
);
currDist = nextDist;
nextDist += step;
}
interpPoints.push( offsetPoint(
ctrlPoints[ ctrlPoints.length - 1 ],
ctrlPoints[ ctrlPoints.length - 1 ][ 0 ] -
ctrlPoints[ ctrlPoints.length - 2 ][ 0 ],
ctrlPoints[ ctrlPoints.length - 1 ][ 1 ] -
ctrlPoints[ ctrlPoints.length - 2 ][ 1 ],
ptOffsetRatios[ ptOffsetRatios.length - 1 ]
));
return interpPoints;
}
mapboxgl.accessToken = 'pk.eyJ1IjoidHdlbGNoIiwiYSI6Il9pX3dtb3cifQ.YcYnsO0X2p3x0HpHPFfleg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v8',
center: [-122.580067, 45.548602],
maxzoom: 20,
zoom: 20
});
var iPathCoords = [
[ -122.579915799999981, 45.5485545, 0.0 ], [ -122.57994530000002, 45.5485536, 0.0 ], [ -122.579944, 45.5485996, 0.0 ], [ -122.580011, 45.54860810000001, 0.0 ], [ -122.5799507, 45.5486043, 0.0 ], [ -122.579954700000016, 45.5485489, 0.0 ], [ -122.579903799999983, 45.5485489, 0.0 ]
];
var iPathLine = turf.linestring(iPathCoords);
var iCoords = interpolateLineRange(iPathCoords, 100, .0000000001);
var iPoints = [];
for (var i=0; i<iCoords.length; i++) {
iPoints.push(turf.point(iCoords[i]));
}
var iPointColl = turf.featurecollection(iPoints);
map.on('style.load', function () {
map.addSource("path", {
"type": "geojson",
"data": iPathLine,
"maxzoom": 20
});
map.addLayer({
"id": "path",
"type": "line",
"source": "path",
"layout": {
"line-join": "round",
"line-cap": "round"
},
"paint": {
"line-color": "#888",
"line-width": 2
}
});
map.addSource("peep", {
"type": "geojson",
"data": iPointColl,
"maxzoom": 20
});
map.addLayer({
"id": "peep",
"type": "circle",
"source": "peep",
"layout": {},
"paint": {
"circle-radius": 3,
"circle-opacity": 0.2
}
});
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment