Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

include index of MultiLineString geometries in nearest-point-on-line #2574

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/turf-nearest-point-on-line/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var addToMap = [line, pt, snapped];
snapped.properties['marker-color'] = '#00f';
```

Returns **[Feature][4]<[Point][1]>** closest point on the `line` to `point`. The properties object will contain three values: `index`: closest point was found on nth line part, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.
Returns **[Feature][4]<[Point][1]>** closest point on the `line` to `point`. The properties object will contain four values: `index`: closest point was found on nth line part, `multiFeatureIndex`: closest point was found on the nth line of the `MultiLineString`, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.

[1]: https://tools.ietf.org/html/rfc7946#section-3.1.2

Expand Down
170 changes: 93 additions & 77 deletions packages/turf-nearest-point-on-line/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { getCoords } from "@turf/invariant";
* @param {Geometry|Feature<Point>|number[]} pt point to snap from
* @param {Object} [options={}] Optional parameters
* @param {string} [options.units='kilometers'] can be degrees, radians, miles, or kilometers
* @returns {Feature<Point>} closest point on the `line` to `point`. The properties object will contain three values: `index`: closest point was found on nth line part, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.
* @returns {Feature<Point>} closest point on the `line` to `point`. The properties object will contain four values: `index`: closest point was found on nth line part, `multiFeatureIndex`: closest point was found on the nth line of the `MultiLineString`, `dist`: distance between pt and the closest point, `location`: distance along the line between start and the closest point.
* @example
* var line = turf.lineString([
* [-77.031669, 38.878605],
Expand All @@ -42,6 +42,7 @@ function nearestPointOnLine<G extends LineString | MultiLineString>(
{
dist: number;
index: number;
multiFeatureIndex: number;
location: number;
[key: string]: any;
}
Expand All @@ -52,96 +53,111 @@ function nearestPointOnLine<G extends LineString | MultiLineString>(

let closestPt: Feature<
Point,
{ dist: number; index: number; location: number }
{ dist: number; index: number; multiFeatureIndex: number; location: number }
> = point([Infinity, Infinity], {
dist: Infinity,
index: -1,
multiFeatureIndex: -1,
location: -1,
});

let length = 0.0;
flattenEach(lines, function (line: any) {
const coords: any = getCoords(line);
flattenEach(
lines,
function (line: any, _featureIndex: number, multiFeatureIndex: number) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add the featureIndex too since we have it?

Copy link
Contributor Author

@andrewharvey andrewharvey Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is having an identity crisis, it's called nearestPointOn**Line** (singular) implying it only accepts one line, however the first argument is named lines (plural) implying it accepts multiple lines, the lines argument type is defined to accept Geometry|Feature<LineString|MultiLineString> which implies only a single line Feature is accepted, albeit you can have a MultiLineString geometry within that single Feature, however it may also accept multiple lines through a GeometryCollection type of Geometry.

Checking the implementation it just passes lines through to flattenEach which accepts FeatureCollection | Feature | Geometry thereby giving rise to the featureIndex.

We should decide that either:

  1. the function is to only accept a single line feature and therefore doesn't need to deal with featureIndex or,
  2. the function can accept multiple features and therefore also include the featureIndex in the result.

While officially supporting FeatureCollection sounds like a good idea, it gets complicated when that FeatureCollection contains multiple geometry types, and defining how the method behaves in those situations is messy. On the other hand we already support GeometryCollection which has the same issue. 🤷

I think for now we go ahead and just include the multiFeatureIndex and leave the decision on featureIndex to another discussion/issue/PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair points, thanks for the detailed writeup!

const coords: any = getCoords(line);

for (let i = 0; i < coords.length - 1; i++) {
//start
const start: Feature<Point, { dist: number }> = point(coords[i]);
start.properties.dist = distance(pt, start, options);
//stop
const stop: Feature<Point, { dist: number }> = point(coords[i + 1]);
stop.properties.dist = distance(pt, stop, options);
// sectionLength
const sectionLength = distance(start, stop, options);
//perpendicular
const heightDistance = Math.max(
start.properties.dist,
stop.properties.dist
);
const direction = bearing(start, stop);
const perpendicularPt1 = destination(
pt,
heightDistance,
direction + 90,
options
);
const perpendicularPt2 = destination(
pt,
heightDistance,
direction - 90,
options
);
const intersect = lineIntersects(
lineString([
perpendicularPt1.geometry.coordinates,
perpendicularPt2.geometry.coordinates,
]),
lineString([start.geometry.coordinates, stop.geometry.coordinates])
);
let intersectPt:
| Feature<Point, { dist: number; location: number }>
| undefined;
for (let i = 0; i < coords.length - 1; i++) {
//start
const start: Feature<Point, { dist: number }> = point(coords[i]);
start.properties.dist = distance(pt, start, options);
//stop
const stop: Feature<Point, { dist: number }> = point(coords[i + 1]);
stop.properties.dist = distance(pt, stop, options);
// sectionLength
const sectionLength = distance(start, stop, options);
//perpendicular
const heightDistance = Math.max(
start.properties.dist,
stop.properties.dist
);
const direction = bearing(start, stop);
const perpendicularPt1 = destination(
pt,
heightDistance,
direction + 90,
options
);
const perpendicularPt2 = destination(
pt,
heightDistance,
direction - 90,
options
);
const intersect = lineIntersects(
lineString([
perpendicularPt1.geometry.coordinates,
perpendicularPt2.geometry.coordinates,
]),
lineString([start.geometry.coordinates, stop.geometry.coordinates])
);
let intersectPt:
| Feature<
Point,
{ dist: number; multiFeatureIndex: number; location: number }
>
| undefined;

if (intersect.features.length > 0 && intersect.features[0]) {
intersectPt = {
...intersect.features[0],
properties: {
dist: distance(pt, intersect.features[0], options),
location: length + distance(start, intersect.features[0], options),
},
};
}
if (intersect.features.length > 0 && intersect.features[0]) {
intersectPt = {
...intersect.features[0],
properties: {
dist: distance(pt, intersect.features[0], options),
multiFeatureIndex: multiFeatureIndex,
location:
length + distance(start, intersect.features[0], options),
},
};
}

if (start.properties.dist < closestPt.properties.dist) {
closestPt = {
...start,
properties: { ...start.properties, index: i, location: length },
};
}
if (start.properties.dist < closestPt.properties.dist) {
closestPt = {
...start,
properties: {
...start.properties,
index: i,
multiFeatureIndex: multiFeatureIndex,
location: length,
},
};
}

if (stop.properties.dist < closestPt.properties.dist) {
closestPt = {
...stop,
properties: {
...stop.properties,
index: i + 1,
location: length + sectionLength,
},
};
}
if (stop.properties.dist < closestPt.properties.dist) {
closestPt = {
...stop,
properties: {
...stop.properties,
index: i + 1,
multiFeatureIndex: multiFeatureIndex,
location: length + sectionLength,
},
};
}

if (
intersectPt &&
intersectPt.properties.dist < closestPt.properties.dist
) {
closestPt = {
...intersectPt,
properties: { ...intersectPt.properties, index: i },
};
if (
intersectPt &&
intersectPt.properties.dist < closestPt.properties.dist
) {
closestPt = {
...intersectPt,
properties: { ...intersectPt.properties, index: i },
};
}
// update length
length += sectionLength;
}
// update length
length += sectionLength;
}
});
);

return closestPt;
}
Expand Down
22 changes: 22 additions & 0 deletions packages/turf-nearest-point-on-line/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,25 @@ test("turf-point-on-line -- Geometry Support", (t) => {
);
t.end();
});

test("turf-point-on-line -- multifeature index", (t) => {
const pt = point([4, 30]);
const multiLine = multiLineString([
[
[7, 50],
[8, 50],
[9, 50],
],
[
[17, 30],
[4, 30],
[2, 30],
],
]);
t.equal(
nearestPointOnLine(multiLine.geometry, pt).properties.multiFeatureIndex,
1,
"multiFeatureIndex"
);
t.end();
});
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
[0, 0, 0, 0, 0, 0, 0, 0]
[
0,
0,
0,
0,
0,
0,
0,
0
]
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
[0.362058, 0.362058, 0.362058, 0.362058]
[
0.362058,
0.362058,
0.362058,
0.362058
]
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
[
[0, 0.162908, 0.362058],
[
0,
435.152731,
848.38422,
1506.62747,
1877.71515,
2362.640364,
2951.524644,
3346.548084,
3711.226929
],
[
0,
0.011338,
0.021306,
0.033964,
0.057049,
0.082838,
0.161154,
0.207251,
0.243733,
0.287901,
0.320556,
0.396017,
0.491916
]
[
0,
0.162908,
0.362058
],
[
0,
435.152731,
848.38422,
1506.62747,
1877.71515,
2362.640364,
2951.524644,
3346.548084,
3711.226929
],
[
0,
0.011338,
0.021306,
0.033964,
0.057049,
0.082838,
0.161154,
0.207251,
0.243733,
0.287901,
0.320556,
0.396017,
0.491916
]
]
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[
0,
0.030566,
0.061133,
0.091699,
0.122265,
0.152831,
0.183398,
0.213964,
0.24453,
0.275097
0,
0.030566,
0.061133,
0.091699,
0.122265,
0.152831,
0.183398,
0.213964,
0.24453,
0.275097
]
Loading
Loading