diff --git a/packages/turf-line-slice-along/README.md b/packages/turf-line-slice-along/README.md index 459e19f4d..0285a10b4 100644 --- a/packages/turf-line-slice-along/README.md +++ b/packages/turf-line-slice-along/README.md @@ -33,6 +33,24 @@ var addToMap = [line, start, stop, sliced] Returns **[Feature][3]<[LineString][1]>** sliced line +## setInterpolatedAltitude + +Assign an interpolated altitude to a coordinate produced by destination(). + +When both segment endpoints carry an altitude the value is linearly +interpolated at `fraction` (0 = at `from`, 1 = at `to`). If either +endpoint has no altitude, any altitude that destination() may have copied +from its origin is removed so the output remains 2-D. + +### Parameters + +* `coord` **[Position][7]** +* `from` **[Position][7]** +* `to` **[Position][7]** +* `fraction` **[number][4]** + +Returns **void** + [1]: https://tools.ietf.org/html/rfc7946#section-3.1.4 [2]: https://tools.ietf.org/html/rfc7946#section-3.1.2 @@ -45,6 +63,8 @@ Returns **[Feature][3]<[LineString][1]>** sliced line [6]: https://turfjs.org/docs/api/types/Units +[7]: https://developer.mozilla.org/docs/Web/API/Position + --- diff --git a/packages/turf-line-slice-along/index.ts b/packages/turf-line-slice-along/index.ts index 6880d7c6c..586a8f006 100644 --- a/packages/turf-line-slice-along/index.ts +++ b/packages/turf-line-slice-along/index.ts @@ -57,7 +57,17 @@ function lineSliceAlong( } direction = bearing(coords[i], coords[i - 1]) - 180; interpolated = destination(coords[i], overshot, direction, { units }); - slice.push(interpolated.geometry.coordinates); + const startIntCoords = interpolated.geometry.coordinates; + const startSegDist = distance(coords[i - 1], coords[i], { units }); + const startFrac = + startSegDist > 0 ? 1 - Math.abs(overshot) / startSegDist : 0; + setInterpolatedAltitude( + startIntCoords, + coords[i - 1], + coords[i], + startFrac + ); + slice.push(startIntCoords); } if (travelled >= stopDist) { @@ -68,7 +78,17 @@ function lineSliceAlong( } direction = bearing(coords[i], coords[i - 1]) - 180; interpolated = destination(coords[i], overshot, direction, { units }); - slice.push(interpolated.geometry.coordinates); + const stopIntCoords = interpolated.geometry.coordinates; + const stopSegDist = distance(coords[i - 1], coords[i], { units }); + const stopFrac = + stopSegDist > 0 ? 1 - Math.abs(overshot) / stopSegDist : 0; + setInterpolatedAltitude( + stopIntCoords, + coords[i - 1], + coords[i], + stopFrac + ); + slice.push(stopIntCoords); return lineString(slice); } @@ -89,6 +109,26 @@ function lineSliceAlong( var last = coords[coords.length - 1]; return lineString([last, last]); } +/** + * Assign an interpolated altitude to a coordinate produced by destination(). + * + * When both segment endpoints carry an altitude the value is linearly + * interpolated at `fraction` (0 = at `from`, 1 = at `to`). If either + * endpoint has no altitude, any altitude that destination() may have copied + * from its origin is removed so the output remains 2-D. + */ +function setInterpolatedAltitude( + coord: Position, + from: Position, + to: Position, + fraction: number +): void { + if (from[2] !== undefined && to[2] !== undefined) { + coord[2] = from[2] + fraction * (to[2] - from[2]); + } else { + coord.splice(2); + } +} export { lineSliceAlong }; export default lineSliceAlong; diff --git a/packages/turf-line-slice-along/test.ts b/packages/turf-line-slice-along/test.ts index 1a0985149..bfd0a1ac0 100644 --- a/packages/turf-line-slice-along/test.ts +++ b/packages/turf-line-slice-along/test.ts @@ -121,3 +121,80 @@ test("turf-line-slice-along -- start equal to line length", function (t) { ); t.end(); }); +// Issue #3007: interpolated endpoint vertices must carry interpolated altitude, +// not the far-endpoint altitude copied by destination(). +test("turf-line-slice-along -- altitude interpolation at stop (issue #3007)", function (t) { + // A ~100 m segment from elevation 0 → 100. + // Slicing at the approximate midpoint (0→50 m) must yield an interpolated + // stop vertex near elevation 50, not the far endpoint's elevation of 100. + const elevLine = { + type: "Feature" as const, + geometry: { + type: "LineString" as const, + coordinates: [ + [0, 0, 0], + [0, 0.0009, 100], // ≈ 100 m north, elevation 100 m + ], + }, + }; + const sliced = lineSliceAlong(elevLine, 0, 50, { units: "meters" }); + const endCoord = + sliced.geometry.coordinates[sliced.geometry.coordinates.length - 1]; + t.equal( + endCoord.length, + 3, + "interpolated stop vertex has altitude component" + ); + t.ok( + Math.abs((endCoord[2] as number) - 50) < 2, + `stop altitude should be ~50 m, got ${endCoord[2]}` + ); + t.end(); +}); + +test("turf-line-slice-along -- altitude interpolation at start (issue #3007)", function (t) { + // Slicing a 3-D segment starting mid-segment: the interpolated start vertex + // must have altitude proportional to position, not a copy of the near endpoint. + const elevLine = { + type: "Feature" as const, + geometry: { + type: "LineString" as const, + coordinates: [ + [0, 0, 0], + [0, 0.0009, 100], // ≈ 100 m, elevation 100 m + ], + }, + }; + // Slice from ≈25 m to end — the interpolated start vertex should be ~alt 25. + const sliced = lineSliceAlong(elevLine, 25, 90, { units: "meters" }); + const startCoord = sliced.geometry.coordinates[0]; + t.equal( + startCoord.length, + 3, + "interpolated start vertex has altitude component" + ); + t.ok( + Math.abs((startCoord[2] as number) - 25) < 2, + `start altitude should be ~25 m, got ${startCoord[2]}` + ); + t.end(); +}); + +test("turf-line-slice-along -- no spurious altitude on 2-D line (issue #3007)", function (t) { + // A 2-D line must produce 2-D interpolated vertices — no altitude should appear. + const line2d = { + type: "Feature" as const, + geometry: { + type: "LineString" as const, + coordinates: [ + [0, 0], + [0, 0.0009], + ], + }, + }; + const sliced = lineSliceAlong(line2d, 0, 50, { units: "meters" }); + const endCoord = + sliced.geometry.coordinates[sliced.geometry.coordinates.length - 1]; + t.equal(endCoord.length, 2, "2-D line produces 2-D interpolated vertex"); + t.end(); +});