Open Armindou opened 7 years ago
@Armindou it seems your "notWorking" coordinates need to be cleaned with @turf/clean-coords
(I noticed there are few duplicated points); this the result.
var line = turf.cleanCoords(turf.lineString([...coordinates...]);
var offsetLine = turf.lineOffset(line, -0.04, "kilometers");
However @rowanwins @DenisCarriere it seems the output of @turf/line-offset
is still off in some places:
@Armindou for your application you might want to consider also @turf/buffer
:
var line = turf.cleanCoords(turf.lineString([...coordinates...]);
var offsetLine = turf.buffer(line, 0.04, "kilometers");
Here the result with one of your lines as an example.
Please be aware though that this module uses jsts
as dependency, an external library that sometimes can generate unexpected results.
What he ( @stebogit ) said :)
I was 90% of the way through the exact same response.
👍 @stebogit Good response, I've noticed this type of behavior a few times as well, no clue how to fix it though... 🤔
I've started work on it and found a partial fix but still doesn't get rid of all kinks. I've got a feeling a better answer lies in a complicated sweepline algorithm...
In this picture I created a lineOffset of 10 meters and a buffer of 10 meters based on Tenison Ave.
It seems that the lineOffset is incorrect (skewed)
(The offset is the blue line)
Hi @jaapster
Unfortunately buffer and offset algorithms aren't as simple as they seem. If you run the same buffer using a variety of tools (eg QGIS vs shapely vs ArcMap) all the outputs will differ slightly.
That said the lineOffset module was just my hack together so there may well be better ways to do it, we've already identified a few shortfalls that Im trying to rectify.
Cheers
@jaapster , @rowanwins -- Yes, there's a definite skew in the line offset algorithm. On the equator, it's fine. At higher latitudes, the offset contracts east-west and expands north-south. If it were doing non-geodesic calculation then one would expect to see the opposite of this effect; instead it appears to be doing geodesic calculations incorrectly.
Hi @nkoren, @rowanwins, I have done some tests and it also looks wrong to me (around 10% error at 40deg latitude). @nkoren: have you been able to work on the fix of the identfied issues? Thanks in advance
Why not use the buffer to generate the offset? If we create the buffer like shown by @jaapster, we simply need to cut the buffer perpendicular to the last and first coordinates. Then we select the line on the left or on the right. Correct me if i'm wrong or if I forget specific cases.
@pyarza Unfortunately, in our case it was easier roll our own geometry library rather than to patch Turf. Sorry!
Hi @nkoren ! If you can give me a guide or a workarround I would be very grateful!
I have problems when the multistring becomes concave!
Thanks!
I've made this pull request #1949 that gives a better result on small (at least several kms) #offsets
function dist2d(coord1, coord2) {
let dx = coord1[0] - coord2[0];
let dy = coord1[1] - coord2[1];
return Math.sqrt(dx * dx + dy * dy)
}
function equals(coord1, coord2) {
let equals = true;
for (let i = coord1.length - 1; i >= 0; --i){
if (coord1[i] != coord2[i]) {
equals = false;
break
}
}
return equals
}
function offsetCoords(coords, offset) {
var path = [];
var N = coords.length-1;
var max = N;
var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1;
var p0, p1, p2;
var isClosed = equals(coords[0],coords[N]);
if (!isClosed) {
p0 = coords[0];
p1 = coords[1];
p2 = [
p0[0] + (p1[1] - p0[1]) / dist2d(p0,p1) *offset,
p0[1] - (p1[0] - p0[0]) / dist2d(p0,p1) *offset
];
path.push(p2);
coords.push(coords[N])
N++;
max--;
}
for (var i = 0; i < max; i++) {
p0 = coords[i];
p1 = coords[(i+1) % N];
p2 = coords[(i+2) % N];
mi = (p1[1] - p0[1])/(p1[0] - p0[0]);
mi1 = (p2[1] - p1[1])/(p2[0] - p1[0]);
// Prevent alignements
if (Math.abs(mi-mi1) > 1e-10) {
li = Math.sqrt((p1[0] - p0[0])*(p1[0] - p0[0])+(p1[1] - p0[1])*(p1[1] - p0[1]));
li1 = Math.sqrt((p2[0] - p1[0])*(p2[0] - p1[0])+(p2[1] - p1[1])*(p2[1] - p1[1]));
ri = p0[0] + offset*(p1[1] - p0[1])/li;
ri1 = p1[0] + offset*(p2[1] - p1[1])/li1;
si = p0[1] - offset*(p1[0] - p0[0])/li;
si1 = p1[1] - offset*(p2[0] - p1[0])/li1;
Xi1 = (mi1*ri1-mi*ri+si-si1) / (mi1-mi);
Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1) / (mi1-mi);
// Correction for vertical lines
if(p1[0] - p0[0] == 0) {
Xi1 = p1[0] + offset*(p1[1] - p0[1])/Math.abs(p1[1] - p0[1]);
Yi1 = mi1*Xi1 - mi1*ri1 + si1;
}
if (p2[0] - p1[0] == 0 ) {
Xi1 = p2[0] + offset*(p2[1] - p1[1])/Math.abs(p2[1] - p1[1]);
Yi1 = mi*Xi1 - mi*ri + si;
}
path.push([Xi1, Yi1]);
}
}
if (isClosed) {
path.push(path[0]);
} else {
coords.pop();
p0 = coords[coords.length-1];
p1 = coords[coords.length-2];
p2 = [
p0[0] - (p1[1] - p0[1]) / dist2d(p0,p1) *offset,
p0[1] + (p1[0] - p0[0]) / dist2d(p0,p1) *offset
];
path.push(p2);
}
return path;
}
function dist2d(coord1, coord2) { let dx = coord1[0] - coord2[0]; let dy = coord1[1] - coord2[1]; return Math.sqrt(dx * dx + dy * dy) } function equals(coord1, coord2) { let equals = true; for (let i = coord1.length - 1; i >= 0; --i){ if (coord1[i] != coord2[i]) { equals = false; break } } return equals } function offsetCoords(coords, offset) { var path = []; var N = coords.length-1; var max = N; var mi, mi1, li, li1, ri, ri1, si, si1, Xi1, Yi1; var p0, p1, p2; var isClosed = equals(coords[0],coords[N]); if (!isClosed) { p0 = coords[0]; p1 = coords[1]; p2 = [ p0[0] + (p1[1] - p0[1]) / dist2d(p0,p1) *offset, p0[1] - (p1[0] - p0[0]) / dist2d(p0,p1) *offset ]; path.push(p2); coords.push(coords[N]) N++; max--; } for (var i = 0; i < max; i++) { p0 = coords[i]; p1 = coords[(i+1) % N]; p2 = coords[(i+2) % N]; mi = (p1[1] - p0[1])/(p1[0] - p0[0]); mi1 = (p2[1] - p1[1])/(p2[0] - p1[0]); // Prevent alignements if (Math.abs(mi-mi1) > 1e-10) { li = Math.sqrt((p1[0] - p0[0])*(p1[0] - p0[0])+(p1[1] - p0[1])*(p1[1] - p0[1])); li1 = Math.sqrt((p2[0] - p1[0])*(p2[0] - p1[0])+(p2[1] - p1[1])*(p2[1] - p1[1])); ri = p0[0] + offset*(p1[1] - p0[1])/li; ri1 = p1[0] + offset*(p2[1] - p1[1])/li1; si = p0[1] - offset*(p1[0] - p0[0])/li; si1 = p1[1] - offset*(p2[0] - p1[0])/li1; Xi1 = (mi1*ri1-mi*ri+si-si1) / (mi1-mi); Yi1 = (mi*mi1*(ri1-ri)+mi1*si-mi*si1) / (mi1-mi); // Correction for vertical lines if(p1[0] - p0[0] == 0) { Xi1 = p1[0] + offset*(p1[1] - p0[1])/Math.abs(p1[1] - p0[1]); Yi1 = mi1*Xi1 - mi1*ri1 + si1; } if (p2[0] - p1[0] == 0 ) { Xi1 = p2[0] + offset*(p2[1] - p1[1])/Math.abs(p2[1] - p1[1]); Yi1 = mi*Xi1 - mi*ri + si; } path.push([Xi1, Yi1]); } } if (isClosed) { path.push(path[0]); } else { coords.pop(); p0 = coords[coords.length-1]; p1 = coords[coords.length-2]; p2 = [ p0[0] - (p1[1] - p0[1]) / dist2d(p0,p1) *offset, p0[1] + (p1[0] - p0[0]) / dist2d(p0,p1) *offset ]; path.push(p2); } return path; }
use this code to solve the problem
Hi guys, im having some issues with the line offset function, it was working ok with a test dataset but then I changed the data and it doesn't work anymore.
The function is returning an array with the first and last coordinate but a bunch of [NaN, NaN] in between. I'm not sure if I have to do something to the data before calling the function, it was working correctly but maybe im missing something.
https://codepen.io/Armindou/pen/dzLgRz?editors=1111
Thanks in advance!
This may be off-topic but the reason im using the line offset is because I need to create a polygon around the route, so what im trying to do is to get 2 offsets (a positive and a negative one), then merge them into a single line and making a polygon of it.