turf
turf copied to clipboard
turf.lineOffset precision
Hello, I'm using line offset to build parallels to short segments (a few kilometers long) but I found an issue with the precision of the function:
const turf = require('@turf/turf')
const line = turf.lineString([ [4.35,50.845], [4.347,50.85] ])
const parallel = turf.lineOffset(line, 1) // kilometer
const distance0 = turf.distance(line.geometry.coordinates[0], parallel.geometry.coordinates[0])
const distance1 = turf.distance(line.geometry.coordinates[1], parallel.geometry.coordinates[1])
console.log(distance0, distance1)
// output: 0.7468818837096454 0.7468398187109684
This can be reproduced at other places in the world. Any idea on how to improve precision? Thanks.
Hi @devleaks
turf/lineOffset
is a bit hacky and has some known issues although that one is a bit surprising. The module probably needs a thorough overhaul.
Hi! It seems that the offset algorithm is directly copied from this stackoverflow thread https://stackoverflow.com/questions/2825412/draw-a-parallel-line This calculation is valid for a Cartesian coordinate system whereas in turf it handles a spherical coord system. This means that the longitude offset is not compensated by the latitude - It gets more squashed the longer from the equator you get.
Can confirm this is what @PereUbu7 says it is. We're using lineOffset to draw rectangles in London and the descrepancy between north/south rectangles and east/west is pretty close to the haversine fudge factor we use for correcting for the latitude of London.
Looks like I'll need to brush up on my maths...
To be honnest, I redid the function I needed, it is quite easy: For line (p1, p2), compute the bearing p1->p2. Add or substract 90° to the bearing. Use "destination" function to compute a point with calculated bearing and supplied distance from p1 and from p2. I understand this might not be a "parallel" in spherical geometry, but for short segments (a dozen kilometer long), it is acceptable. I will not work for larger segments. Regards. P.
I made a PR for this: https://github.com/Turfjs/turf/pull/1949 But couldn't get my env going, so it's untested.
Here's my code to get correct parallel line with more than 2 points :
const getLineOffset = (line, distance, { units = 'kilometers' } = {}) => {
const lineCoords = line.geometry.coordinates;
const transformAngle = distance < 0 ? -90 : 90;
if (distance < 0) distance = -distance;
const offsetLines = [];
for (let i = 0; i < lineCoords.length - 1; i++) { // Translating each segment of the line to correct position
const angle = bearing(lineCoords[i], lineCoords[i + 1]) + transformAngle;
const firstPoint = transformTranslate(point(lineCoords[i]), distance, angle, { units })?.geometry.coordinates;
const secondPoint = transformTranslate(point(lineCoords[i + 1]), distance, angle, { units })?.geometry.coordinates;
offsetLines.push([firstPoint, secondPoint]);
}
const offsetCoords = [offsetLines[0][0]]; // First point inserted
for (let i = 0; i < offsetLines.length; i++) { // For each translated segment of the initial line
if (offsetLines[i + 1]) { // If there's another segment after this one
const firstLine = transformScale(lineString(offsetLines[i]), 2); // transformScale is useful in case the two segment don't have an intersection point
const secondLine = transformScale(lineString(offsetLines[i + 1]), 2); // Which happen when the resulting offset line is bigger than the initial one
// We're calculating the intersection point between the two translated & scaled segments
offsetCoords.push(lineIntersect(firstLine, secondLine).features[0].geometry.coordinates);
} else offsetCoords.push(offsetLines[i][1]); // If there's no other segment after this one, we simply push the last point of the line
}
return lineString(offsetCoords);
};