From b9491fbd8c9bba3a1cdc1e7fb8ed4265d8de1e43 Mon Sep 17 00:00:00 2001 From: Matthew Fedderly <24275386+mfedderly@users.noreply.github.com> Date: Fri, 19 Dec 2025 06:15:24 -0500 Subject: [PATCH 1/2] Copy featureReduce existing js and ts and merge them --- packages/turf-area/index.ts | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/turf-area/index.ts b/packages/turf-area/index.ts index ff016ec7bc..ffd77b4744 100644 --- a/packages/turf-area/index.ts +++ b/packages/turf-area/index.ts @@ -1,6 +1,39 @@ -import { Feature, FeatureCollection, Geometry } from "geojson"; +import { + Feature, + FeatureCollection, + GeoJsonProperties, + Geometry, + GeometryCollection, + GeometryObject, +} from "geojson"; import { earthRadius } from "@turf/helpers"; -import { geomReduce } from "@turf/meta"; +import { geomReduce, featureEach } from "@turf/meta"; + +// would be in @turf/meta +export function featureReduce< + Reducer, + G extends GeometryObject, + P extends GeoJsonProperties = GeoJsonProperties, +>( + geojson: + | Feature + | FeatureCollection + | Feature, + callback: ( + previousValue: Reducer, + currentFeature: Feature, + featureIndex: number + ) => Reducer, + initialValue?: Reducer +) { + var previousValue = initialValue; + featureEach(geojson, function (currentFeature, featureIndex) { + if (featureIndex === 0 && initialValue === undefined) + previousValue = currentFeature; + else previousValue = callback(previousValue, currentFeature, featureIndex); + }); + return previousValue; +} /** * Calculates the geodesic area in square meters of one or more polygons. From e607a4dcedcfd6f0aa7f81738b5db023c8282301 Mon Sep 17 00:00:00 2001 From: Matthew Fedderly <24275386+mfedderly@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:55:25 -0500 Subject: [PATCH 2/2] Sketch of a 'properly typed' featureReduce --- packages/turf-area/index.ts | 117 ++++++++++++++++++++++++++++++------ 1 file changed, 97 insertions(+), 20 deletions(-) diff --git a/packages/turf-area/index.ts b/packages/turf-area/index.ts index ffd77b4744..8944ae22fb 100644 --- a/packages/turf-area/index.ts +++ b/packages/turf-area/index.ts @@ -3,38 +3,115 @@ import { FeatureCollection, GeoJsonProperties, Geometry, - GeometryCollection, - GeometryObject, + Point, } from "geojson"; import { earthRadius } from "@turf/helpers"; import { geomReduce, featureEach } from "@turf/meta"; // would be in @turf/meta -export function featureReduce< - Reducer, - G extends GeometryObject, - P extends GeoJsonProperties = GeoJsonProperties, ->( - geojson: - | Feature - | FeatureCollection - | Feature, + +// with initial value specified, things are relatively straightforward +export function featureReduce( + geojson: T, + callback: ( + previousValue: R, + currentFeature: T extends FeatureCollection + ? Feature + : T, + featureIndex: number + ) => R, + initialValue: R +): R; + +// with no initial value specified, previousValue can sometimes be Feature +// the return value may also be undefined if we get a FeatureCollection without a guaranteed first element +export function featureReduce( + geojson: T, + callback: ( + previousValue: T extends FeatureCollection + ? Feature + : T, + currentFeature: T extends FeatureCollection + ? Feature + : T, + featureIndex: number + ) => T extends FeatureCollection ? Feature : T +): T extends Feature + ? T + : T extends { features: [Feature] } + ? Feature + : T extends FeatureCollection + ? Feature | undefined + : never; + +export function featureReduce( + geojson: T, callback: ( - previousValue: Reducer, - currentFeature: Feature, + previousValue: R | T extends FeatureCollection + ? Feature + : T, + currentFeature: T extends FeatureCollection + ? Feature + : T, featureIndex: number - ) => Reducer, - initialValue?: Reducer -) { - var previousValue = initialValue; + ) => R, + initialValue?: R +): T extends Feature ? R : R | undefined { + var previousValue: R | T extends FeatureCollection + ? Feature + : T = initialValue as any; featureEach(geojson, function (currentFeature, featureIndex) { if (featureIndex === 0 && initialValue === undefined) - previousValue = currentFeature; - else previousValue = callback(previousValue, currentFeature, featureIndex); + previousValue = currentFeature as any; + else + previousValue = callback( + previousValue, + currentFeature as any, + featureIndex + ) as any; }); - return previousValue; + return previousValue as any; } +const feature: Feature = { + type: "Feature", + geometry: { type: "Point", coordinates: [0, 0] }, + properties: {}, +}; + +const featureCollection: FeatureCollection = { + type: "FeatureCollection", + features: [feature], +}; + +const emptyFeatureCollection: FeatureCollection & { features: never[] } = + { + type: "FeatureCollection", + features: [], + }; + +// a FeatureCollection where we know that there is at least one feature (somehow) +const populatedFeatureCollection: { + type: "FeatureCollection"; + features: [Feature]; +} = { + type: "FeatureCollection", + features: [feature], +}; + +const r1 = featureReduce(feature, (_acc, f, _i) => f); +const r2 = featureReduce(feature, (acc, _f, _i) => acc + 1, 0); +const r3 = featureReduce(featureCollection, (acc, f, _i) => f); +const r4 = featureReduce(featureCollection, (acc, _f, _i) => acc + 1, 0); +const r5 = featureReduce(emptyFeatureCollection, (acc, f, _i) => f); +const r6 = featureReduce(emptyFeatureCollection, (acc, _f, _i) => acc + 1, 0); +const r7 = featureReduce(populatedFeatureCollection, (acc, f, _i) => f); +const r8 = featureReduce( + populatedFeatureCollection, + (acc, _f, _i) => acc + 1, + 0 +); + /** * Calculates the geodesic area in square meters of one or more polygons. *