From 24752b33d9e560038f17afb0b190f0a98e40f576 Mon Sep 17 00:00:00 2001 From: Alejandro Diaz Paredes <89232857+adiazpar@users.noreply.github.com> Date: Thu, 1 Oct 2026 21:10:47 -0700 Subject: [PATCH] Fix exact-point values in turf-interpolate Stop distance weighting when a grid point coincides with an input sample. Add regressions for sample order, value sources, weights, and grid types. Fixes #2743. Related to #1601. AI assistance: OpenAI's assistant prepared this patch and tests. --- packages/turf-interpolate/index.ts | 3 + packages/turf-interpolate/test.ts | 99 ++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/packages/turf-interpolate/index.ts b/packages/turf-interpolate/index.ts index 04155062bf..436eb5ce41 100644 --- a/packages/turf-interpolate/index.ts +++ b/packages/turf-interpolate/index.ts @@ -119,7 +119,10 @@ function interpolate( throw new Error("zValue is missing"); } if (d === 0) { + // Use the exact input value instead of distance weighting. + sw = 1; zw = zValue; + return false; } var w = 1.0 / Math.pow(d, weight); sw += w; diff --git a/packages/turf-interpolate/test.ts b/packages/turf-interpolate/test.ts index 3fb1b22c16..0a2a639b07 100644 --- a/packages/turf-interpolate/test.ts +++ b/packages/turf-interpolate/test.ts @@ -93,6 +93,15 @@ test("turf-interpolate -- throws errors", (t) => { /zValue is missing/, "zValue is missing" ); + t.throws( + () => + interpolate(featureCollection([point([0, 0])]), 1000, { + gridType: "point", + bbox: [-1, -1, 1, 1], + }), + /zValue is missing/, + "an exact match still requires a zValue" + ); t.end(); }); @@ -110,6 +119,96 @@ test("turf-interpolate -- zValue from 3rd coordinate", (t) => { t.end(); }); +test("turf-interpolate -- exact match preserves the input value", (t) => { + for (const elevation of [42, 0, -10]) { + for (const matchIndex of [0, 1, 2]) { + const samples = [ + point([1, 1], { elevation: 100 }), + point([-1, 1], { elevation: 200 }), + ]; + samples.splice(matchIndex, 0, point([0, 0, 999], { elevation })); + const points = featureCollection(samples); + const original = JSON.stringify(points); + const result = interpolate(points, 1000, { + gridType: "point", + bbox: [-1, -1, 1, 1], + }); + + t.equal(result.features.length, 1, "one grid point"); + t.deepEqual(result.features[0].geometry.coordinates, [0, 0]); + t.equal( + result.features[0].properties.elevation, + elevation, + `exact value ${elevation} at input index ${matchIndex}` + ); + t.equal(JSON.stringify(points), original, "input is not modified"); + } + } + t.end(); +}); + +test("turf-interpolate -- exact match with different weights", (t) => { + const points = featureCollection([ + point([1, 1], { elevation: 100 }), + point([0, 0], { elevation: 42 }), + ]); + for (const weight of [0.5, 2, 0, -1]) { + for (const features of [points.features, [...points.features].reverse()]) { + const result = interpolate(featureCollection(features), 1000, { + gridType: "point", + bbox: [-1, -1, 1, 1], + weight, + }); + t.equal(result.features[0].properties.elevation, 42, `weight ${weight}`); + } + } + t.end(); +}); + +test("turf-interpolate -- exact match uses the third coordinate fallback", (t) => { + const result = interpolate(featureCollection([point([0, 0, -10])]), 1000, { + gridType: "point", + bbox: [-1, -1, 1, 1], + property: "temperature", + }); + t.equal(result.features[0].properties.temperature, -10); + t.end(); +}); + +test("turf-interpolate -- exact match at a polygon centroid", (t) => { + const result = interpolate( + featureCollection([ + point([1, 1], { elevation: 100 }), + point([0, 0], { elevation: 42 }), + ]), + 200, + { bbox: [-1, -1, 1, 1] } + ); + t.equal(result.features.length, 1, "one square grid cell"); + t.equal(result.features[0].properties.elevation, 42); + t.end(); +}); + +test("turf-interpolate -- nonmatching points retain distance weighting", (t) => { + const points = featureCollection([ + point([-1, 0], { elevation: 20 }), + point([1, 0], { elevation: 40 }), + ]); + for (const weight of [0, 1, 2, -1]) { + const result = interpolate(points, 1000, { + gridType: "point", + bbox: [-1, -1, 1, 1], + weight, + }); + t.equal( + round(result.features[0].properties.elevation), + 30, + `weight ${weight}` + ); + } + t.end(); +}); + // style result function colorize(grid, property, name) { property = property || "elevation";