From 67cc3d4140ddaeac8558da3745527a19734cfbed Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Tue, 30 Jun 2026 13:07:34 +0900 Subject: [PATCH] fix(@turf/midpoint): interpolate altitude (z-coordinate) for 3D points When both input points have a z-coordinate (altitude), the midpoint should return a point whose z equals the average (z1 + z2) / 2. Previously, destination() was called with point1 as the origin, which propagated point1's z unchanged to the result. The fix extracts both z values and assigns the linear average when both are present. Adds @turf/invariant as an explicit dependency (already a transitive dep via @turf/destination) to access getCoord(). --- packages/turf-midpoint/index.ts | 9 +++++++++ packages/turf-midpoint/package.json | 1 + packages/turf-midpoint/test.ts | 31 +++++++++++++++++++++++++++++ pnpm-lock.yaml | 9 +++++++++ 4 files changed, 50 insertions(+) diff --git a/packages/turf-midpoint/index.ts b/packages/turf-midpoint/index.ts index 70ccef3f5a..6bb791de07 100644 --- a/packages/turf-midpoint/index.ts +++ b/packages/turf-midpoint/index.ts @@ -3,6 +3,7 @@ import { bearing } from "@turf/bearing"; import { destination } from "@turf/destination"; import { distance } from "@turf/distance"; import { Coord } from "@turf/helpers"; +import { getCoord } from "@turf/invariant"; /** * Takes two points and returns a point midway between them. The midpoint is @@ -28,6 +29,14 @@ function midpoint(point1: Coord, point2: Coord): Feature { const heading = bearing(point1, point2); const midpoint = destination(point1, dist / 2, heading); + // Interpolate altitude (z-coordinate) when both input points carry one. + // destination() propagates only the origin's z unchanged; average them instead. + const coord1 = getCoord(point1); + const coord2 = getCoord(point2); + if (coord1[2] !== undefined && coord2[2] !== undefined) { + midpoint.geometry.coordinates[2] = (coord1[2] + coord2[2]) / 2; + } + return midpoint; } diff --git a/packages/turf-midpoint/package.json b/packages/turf-midpoint/package.json index f3f24b63a3..9fb062f0f2 100644 --- a/packages/turf-midpoint/package.json +++ b/packages/turf-midpoint/package.json @@ -64,6 +64,7 @@ "@turf/destination": "workspace:*", "@turf/distance": "workspace:*", "@turf/helpers": "workspace:*", + "@turf/invariant": "workspace:*", "@types/geojson": "catalog:", "tslib": "catalog:" } diff --git a/packages/turf-midpoint/test.ts b/packages/turf-midpoint/test.ts index a55881ec30..bc7019869e 100644 --- a/packages/turf-midpoint/test.ts +++ b/packages/turf-midpoint/test.ts @@ -68,3 +68,34 @@ test("midpoint -- long distance", function (t) { t.end(); }); +test("midpoint -- interpolates altitude (z-coordinate) from 3D points", function (t) { + var pt1 = point([0, 0, 100]); + var pt2 = point([10, 0, 200]); + + var mid = midpoint(pt1, pt2); + var coords = mid.geometry.coordinates; + + // The midpoint of two 3D points must interpolate z as the average + t.equal( + coords[2], + 150, + "z should be the average of the two input altitudes (100+200)/2 = 150" + ); + + t.end(); +}); + +test("midpoint -- returns undefined z when inputs have no altitude", function (t) { + var pt1 = point([0, 0]); + var pt2 = point([10, 0]); + + var mid = midpoint(pt1, pt2); + + t.equal( + mid.geometry.coordinates[2], + undefined, + "z should be undefined when inputs have no altitude" + ); + + t.end(); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac1488206e..7bb37ee1b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4173,6 +4173,9 @@ importers: '@turf/helpers': specifier: workspace:* version: link:../turf-helpers + '@turf/invariant': + specifier: workspace:* + version: link:../turf-invariant '@types/geojson': specifier: 'catalog:' version: 7946.0.14 @@ -7288,6 +7291,7 @@ packages: '@lerna/create@9.0.3': resolution: {integrity: sha512-hUTEWrR8zH+/Z3bp/R1aLm6DW8vB/BB7KH7Yeg4fMfrvSwxegiLVW9uJFAzWkK4mzEagmj/Dti85Yg9MN13t0g==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + deprecated: This package is an implementation detail of Lerna and is no longer published separately. '@ljharb/resumer@0.1.3': resolution: {integrity: sha512-d+tsDgfkj9X5QTriqM4lKesCkMMJC3IrbPKHvayP00ELx2axdXvDfWkqjxrLXIzGcQzmj7VAUT1wopqARTvafw==} @@ -8443,6 +8447,7 @@ packages: conventional-changelog-core@5.0.1: resolution: {integrity: sha512-Rvi5pH+LvgsqGwZPZ3Cq/tz4ty7mjijhr3qR4m9IBXNbxGGYgTVVO+duXzz9aArmHxFtwZ+LRkrNIMDQzgoY4A==} engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Please use conventional-changelog instead. conventional-changelog-preset-loader@3.0.0: resolution: {integrity: sha512-qy9XbdSLmVnwnvzEisjxdDiLA4OmV3o8db+Zdg4WiFw14fP3B6XNz98X0swPPpkTd/pc1K7+adKgEDM1JCUMiA==} @@ -9158,6 +9163,7 @@ packages: git-raw-commits@3.0.0: resolution: {integrity: sha512-b5OHmZ3vAgGrDn/X0kS+9qCfNKWe4K/jFnhwzVWWg0/k5eLa3060tZShrRg8Dja5kPc+YjS0Gc6y7cRr44Lpjw==} engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-remote-origin-url@2.0.0: @@ -9167,6 +9173,7 @@ packages: git-semver-tags@5.0.1: resolution: {integrity: sha512-hIvOeZwRbQ+7YEUmCkHqo8FOLQZCEn18yevLHADlFPZY02KJGsu5FZt9YW/lybfK2uhWFI7Qg/07LekJiTv7iA==} engines: {node: '>=14'} + deprecated: Deprecated and no longer maintained. Use @conventional-changelog/git-client instead. hasBin: true git-up@7.0.0: @@ -11361,10 +11368,12 @@ packages: tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me tar@7.5.2: resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me temp-dir@1.0.0: resolution: {integrity: sha512-xZFXEGbG7SNC3itwBzI3RYjq/cEhBkx2hJuKGIUOcEULmkQExXiHat2z/qkISYsuR+IKumhEfKKbV5qXmhICFQ==}