Turfjs / turf

A modular geospatial engine written in JavaScript and TypeScript
https://turfjs.org/
MIT License
9.33k stars 943 forks source link

Problem with line offset #933

Open Armindou opened 7 years ago

Armindou commented 7 years ago

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.

stebogit commented 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:

screen shot 2017-09-05 at 9 48 09 pm

screen shot 2017-09-05 at 9 49 51 pm

@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.

rowanwins commented 7 years ago

What he ( @stebogit ) said :)

I was 90% of the way through the exact same response.

DenisCarriere commented 7 years ago

👍 @stebogit Good response, I've noticed this type of behavior a few times as well, no clue how to fix it though... 🤔

rowanwins commented 7 years ago

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...

jaapster commented 7 years ago

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)

screen shot 2017-11-07 at 09 12 14

(The offset is the blue line)

rowanwins commented 7 years ago

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

nkoren commented 6 years ago

@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.

pyarza commented 6 years ago

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

kaligrafy commented 5 years ago

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.

nkoren commented 5 years ago

@pyarza Unfortunately, in our case it was easier roll our own geometry library rather than to patch Turf. Sorry!

AntonyFagundezStk commented 4 years ago

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!

PereUbu7 commented 4 years ago

I've made this pull request #1949 that gives a better result on small (at least several kms) #offsets

redrockhorse commented 2 years ago
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;
}
redrockhorse commented 2 years ago
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