From 85b5c74cc8e86a1be82d1a75890ae8d0ba45b518 Mon Sep 17 00:00:00 2001 From: James Beard Date: Tue, 3 Feb 2026 14:27:39 +1100 Subject: [PATCH] Reverted backward incompatible fix to nearestPointOnLine introduced by PR #2951, while still keeping lineSlice issue #2946 resolved with a temporary workaround. --- packages/turf-line-slice/index.ts | 32 +++++++++-- .../in/avoid-duplicated-end-points.geojson | 33 ++++++++++++ .../out/avoid-duplicated-end-points.geojson | 47 ++++++++++++++++ packages/turf-nearest-point-on-line/index.ts | 9 ++-- packages/turf-nearest-point-on-line/test.ts | 2 +- .../test/in/end-point-1.geojson | 30 ----------- .../test/in/end-point-2.geojson | 29 ---------- .../test/out/end-point-1.geojson | 53 ------------------- .../test/out/end-point-2.geojson | 52 ------------------ .../test/out/multiLine1.geojson | 4 +- 10 files changed, 114 insertions(+), 177 deletions(-) create mode 100644 packages/turf-line-slice/test/in/avoid-duplicated-end-points.geojson create mode 100644 packages/turf-line-slice/test/out/avoid-duplicated-end-points.geojson delete mode 100644 packages/turf-nearest-point-on-line/test/in/end-point-1.geojson delete mode 100644 packages/turf-nearest-point-on-line/test/in/end-point-2.geojson delete mode 100644 packages/turf-nearest-point-on-line/test/out/end-point-1.geojson delete mode 100644 packages/turf-nearest-point-on-line/test/out/end-point-2.geojson diff --git a/packages/turf-line-slice/index.ts b/packages/turf-line-slice/index.ts index 0c30b96732..d7294b0bbf 100644 --- a/packages/turf-line-slice/index.ts +++ b/packages/turf-line-slice/index.ts @@ -1,7 +1,7 @@ import { getCoords, getType } from "@turf/invariant"; import { Coord, lineString as linestring } from "@turf/helpers"; import { nearestPointOnLine } from "@turf/nearest-point-on-line"; -import { Feature, LineString } from "geojson"; +import type { Feature, LineString, Point } from "geojson"; /** * Takes a {@link LineString|line}, a start {@link Point}, and a stop point @@ -44,14 +44,20 @@ function lineSlice( const startVertex = nearestPointOnLine(line, startPt); const stopVertex = nearestPointOnLine(line, stopPt); + + // Workaround until we can fix backwards incompatible nearestPointOnLine bug + // #3016 + fixSegmentIndexBounds(line, startVertex); + fixSegmentIndexBounds(line, stopVertex); + const ends = - startVertex.properties.index <= stopVertex.properties.index + startVertex.properties.segmentIndex <= stopVertex.properties.segmentIndex ? [startVertex, stopVertex] : [stopVertex, startVertex]; const clipCoords = [ends[0].geometry.coordinates]; for ( - let i = ends[0].properties.index + 1; - i < ends[1].properties.index + 1; + let i = ends[0].properties.segmentIndex + 1; + i < ends[1].properties.segmentIndex + 1; i++ ) { clipCoords.push(coords[i]); @@ -60,5 +66,23 @@ function lineSlice( return linestring(clipCoords, line.type === "Feature" ? line.properties : {}); } +function fixSegmentIndexBounds( + line: Feature | LineString, + vertex: Feature +) { + // There is a bug in nearestPointOnLine where the returned segmentIndex can + // refer to a non-existent segment (overflows). Until we can fix that bug + // (see https://github.com/Turfjs/turf/issues/3016) we adjust the + // segmentIndex here to make sure it's in range. + + let geometry: LineString = line.type === "Feature" ? line.geometry : line; + + if (vertex.properties.segmentIndex >= geometry.coordinates.length - 1) { + // segmentIndex refers to a non-existent segment beyond the end of the + // lineString. Override it. + vertex.properties.segmentIndex = geometry.coordinates.length - 2; + } +} + export { lineSlice }; export default lineSlice; diff --git a/packages/turf-line-slice/test/in/avoid-duplicated-end-points.geojson b/packages/turf-line-slice/test/in/avoid-duplicated-end-points.geojson new file mode 100644 index 0000000000..75d1bdcf10 --- /dev/null +++ b/packages/turf-line-slice/test/in/avoid-duplicated-end-points.geojson @@ -0,0 +1,33 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [3, 0], + [2, -1], + [2, 2] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [2, 0] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [2, 2] + } + } + ] +} diff --git a/packages/turf-line-slice/test/out/avoid-duplicated-end-points.geojson b/packages/turf-line-slice/test/out/avoid-duplicated-end-points.geojson new file mode 100644 index 0000000000..feaff304a0 --- /dev/null +++ b/packages/turf-line-slice/test/out/avoid-duplicated-end-points.geojson @@ -0,0 +1,47 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "LineString", + "coordinates": [ + [3, 0], + [2, -1], + [2, 2] + ] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [2, 0] + } + }, + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Point", + "coordinates": [2, 2] + } + }, + { + "type": "Feature", + "properties": { + "stroke": "#f0f", + "stroke-width": 6 + }, + "geometry": { + "type": "LineString", + "coordinates": [ + [2, 0], + [2, 2] + ] + } + } + ] +} diff --git a/packages/turf-nearest-point-on-line/index.ts b/packages/turf-nearest-point-on-line/index.ts index 95307d23c8..70669d156a 100644 --- a/packages/turf-nearest-point-on-line/index.ts +++ b/packages/turf-nearest-point-on-line/index.ts @@ -106,7 +106,6 @@ function nearestPointOnLine( } const coords: any = getCoords(line); - const maxSegmentIndex = coords.length - 2; for (let i = 0; i < coords.length - 1; i++) { //start - start of current line section @@ -144,11 +143,9 @@ function nearestPointOnLine( const segmentDistance = distance(start, intersectPos, options); closestPt = point(intersectPos, { lineStringIndex: lineStringIndex, - // Legacy behaviour where index progresses to next segment if we - // went with the end point this iteration. Though make sure we - // only progress to the beginning of the next segment if one - // actually exists. - segmentIndex: wasEnd && i + 1 <= maxSegmentIndex ? i + 1 : i, + // Legacy behaviour where index progresses to next segment # if we + // went with the end point this iteration. + segmentIndex: wasEnd ? i + 1 : i, totalDistance: totalDistance + segmentDistance, lineDistance: lineDistance + segmentDistance, segmentDistance: segmentDistance, diff --git a/packages/turf-nearest-point-on-line/test.ts b/packages/turf-nearest-point-on-line/test.ts index 5ca076096f..e3d1e969a8 100644 --- a/packages/turf-nearest-point-on-line/test.ts +++ b/packages/turf-nearest-point-on-line/test.ts @@ -333,7 +333,7 @@ test("turf-nearest-point-on-line - segmentIndex and pointDistance", (t) => { const pt = point([-92.110576, 41.040649]); const snapped = truncate(nearestPointOnLine(line, pt)); - t.equal(snapped.properties.segmentIndex, 7, "properties.segmentIndex"); + t.equal(snapped.properties.segmentIndex, 8, "properties.segmentIndex"); t.equal( Number(snapped.properties.pointDistance.toFixed(6)), 0.823802, diff --git a/packages/turf-nearest-point-on-line/test/in/end-point-1.geojson b/packages/turf-nearest-point-on-line/test/in/end-point-1.geojson deleted file mode 100644 index 670195d226..0000000000 --- a/packages/turf-nearest-point-on-line/test/in/end-point-1.geojson +++ /dev/null @@ -1,30 +0,0 @@ -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "description": "Result index should be 2 as there is a third segment and matches to an endpoint bump to the start of the next segment" - }, - "geometry": { - "coordinates": [ - [151.12342, -29.776038], - [151.112785, -29.783069], - [151.119935, -29.764135], - [151.107173, -29.768495] - ], - "type": "LineString" - } - }, - { - "type": "Feature", - "properties": { - "marker-color": "green" - }, - "geometry": { - "coordinates": [151.12207, -29.761332], - "type": "Point" - } - } - ] -} diff --git a/packages/turf-nearest-point-on-line/test/in/end-point-2.geojson b/packages/turf-nearest-point-on-line/test/in/end-point-2.geojson deleted file mode 100644 index 74e27532a9..0000000000 --- a/packages/turf-nearest-point-on-line/test/in/end-point-2.geojson +++ /dev/null @@ -1,29 +0,0 @@ -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "description": "Result index should be 1 as there is no later segment to bump the index to." - }, - "geometry": { - "coordinates": [ - [151.12342, -29.776038], - [151.112785, -29.783069], - [151.119935, -29.764135] - ], - "type": "LineString" - } - }, - { - "type": "Feature", - "properties": { - "marker-color": "green" - }, - "geometry": { - "coordinates": [151.12207, -29.761332], - "type": "Point" - } - } - ] -} diff --git a/packages/turf-nearest-point-on-line/test/out/end-point-1.geojson b/packages/turf-nearest-point-on-line/test/out/end-point-1.geojson deleted file mode 100644 index b453251c42..0000000000 --- a/packages/turf-nearest-point-on-line/test/out/end-point-1.geojson +++ /dev/null @@ -1,53 +0,0 @@ -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "description": "Result index should be 2 as there is a third segment and matches to an endpoint bump to the start of the next segment" - }, - "geometry": { - "coordinates": [ - [151.12342, -29.776038], - [151.112785, -29.783069], - [151.119935, -29.764135], - [151.107173, -29.768495] - ], - "type": "LineString" - } - }, - { - "type": "Feature", - "properties": { "stroke": "#F00", "stroke-width": 6 }, - "geometry": { - "type": "LineString", - "coordinates": [ - [151.119935, -29.764135], - [151.12207, -29.761332] - ] - } - }, - { - "type": "Feature", - "properties": { "marker-color": "green" }, - "geometry": { "coordinates": [151.12207, -29.761332], "type": "Point" } - }, - { - "type": "Feature", - "properties": { - "dist": 0.373652, - "location": 3.505821, - "index": 2, - "multiFeatureIndex": 0, - "lineStringIndex": 0, - "segmentIndex": 2, - "totalDistance": 3.505821, - "lineDistance": 3.505821, - "segmentDistance": 2.215582, - "pointDistance": 0.373652, - "marker-color": "#F0F" - }, - "geometry": { "type": "Point", "coordinates": [151.119935, -29.764135] } - } - ] -} diff --git a/packages/turf-nearest-point-on-line/test/out/end-point-2.geojson b/packages/turf-nearest-point-on-line/test/out/end-point-2.geojson deleted file mode 100644 index 4f4f0fd4f4..0000000000 --- a/packages/turf-nearest-point-on-line/test/out/end-point-2.geojson +++ /dev/null @@ -1,52 +0,0 @@ -{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "description": "Result index should be 1 as there is no later segment to bump the index to." - }, - "geometry": { - "coordinates": [ - [151.12342, -29.776038], - [151.112785, -29.783069], - [151.119935, -29.764135] - ], - "type": "LineString" - } - }, - { - "type": "Feature", - "properties": { "stroke": "#F00", "stroke-width": 6 }, - "geometry": { - "type": "LineString", - "coordinates": [ - [151.119935, -29.764135], - [151.12207, -29.761332] - ] - } - }, - { - "type": "Feature", - "properties": { "marker-color": "green" }, - "geometry": { "coordinates": [151.12207, -29.761332], "type": "Point" } - }, - { - "type": "Feature", - "properties": { - "dist": 0.373652, - "location": 3.505821, - "index": 1, - "multiFeatureIndex": 0, - "lineStringIndex": 0, - "segmentIndex": 1, - "totalDistance": 3.505821, - "lineDistance": 3.505821, - "segmentDistance": 2.215582, - "pointDistance": 0.373652, - "marker-color": "#F0F" - }, - "geometry": { "type": "Point", "coordinates": [151.119935, -29.764135] } - } - ] -} diff --git a/packages/turf-nearest-point-on-line/test/out/multiLine1.geojson b/packages/turf-nearest-point-on-line/test/out/multiLine1.geojson index c593b21276..ad4ceee464 100644 --- a/packages/turf-nearest-point-on-line/test/out/multiLine1.geojson +++ b/packages/turf-nearest-point-on-line/test/out/multiLine1.geojson @@ -68,7 +68,7 @@ "type": "Feature", "properties": { "lineStringIndex": 1, - "segmentIndex": 20, + "segmentIndex": 21, "totalDistance": 9479.011715, "lineDistance": 4800.716022, "segmentDistance": 173.221741, @@ -76,7 +76,7 @@ "dist": 114.725451, "multiFeatureIndex": 1, "location": 9479.011715, - "index": 20, + "index": 21, "marker-color": "#F0F" }, "geometry": {