Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 112 additions & 2 deletions packages/turf-area/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,116 @@
import { Feature, FeatureCollection, Geometry } from "geojson";
import {
Feature,
FeatureCollection,
GeoJsonProperties,
Geometry,
Point,
} from "geojson";
import { earthRadius } from "@turf/helpers";
import { geomReduce } from "@turf/meta";
import { geomReduce, featureEach } from "@turf/meta";

// would be in @turf/meta
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly just pay attention to the two function typings, not the one with the actual implementation itself

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference from lib.es5.d.ts on how Array.prototype.reduce is typed. Note that you can either specify an initial value and deal in that type for the reduce callbacks, or you can omit it and you are forced to use the shape of whatever is in the array.

    /**
     * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
     * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
     * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
     */
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
    reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
    /**
     * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
     * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
     * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
     */
    reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;


// with initial value specified, things are relatively straightforward
export function featureReduce<R, T extends Feature | FeatureCollection>(
geojson: T,
callback: (
previousValue: R,
currentFeature: T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T,
featureIndex: number
) => R,
initialValue: R
): R;

// with no initial value specified, previousValue can sometimes be Feature<G,P>
// the return value may also be undefined if we get a FeatureCollection without a guaranteed first element
export function featureReduce<T extends Feature | FeatureCollection>(
geojson: T,
callback: (
previousValue: T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T,
currentFeature: T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T,
featureIndex: number
) => T extends FeatureCollection<infer G, infer P> ? Feature<G, P> : T
): T extends Feature
? T
: T extends { features: [Feature<infer G, infer P>] }
? Feature<G, P>
: T extends FeatureCollection<infer G, infer P>
? Feature<G, P> | undefined
: never;

export function featureReduce<R, T extends Feature | FeatureCollection>(
geojson: T,
callback: (
previousValue: R | T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T,
currentFeature: T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T,
featureIndex: number
) => R,
initialValue?: R
): T extends Feature ? R : R | undefined {
var previousValue: R | T extends FeatureCollection<infer G, infer P>
? Feature<G, P>
: T = initialValue as any;
featureEach(geojson, function (currentFeature, featureIndex) {
if (featureIndex === 0 && initialValue === undefined)
previousValue = currentFeature as any;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this is actually different from how Array.prototype.reduce works when you don't specify an initial value. Without an initial value, you need to set previousValue to the first feature, but then only iterate 1...n in the array.

I think in order to know whether or not we were passed an initial value, we need to use arguments.length to see if we got an explicit undefined for initialValue vs a parameter that wasn't specified at all.

else
previousValue = callback(
previousValue,
currentFeature as any,
featureIndex
) as any;
});
return previousValue as any;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a bunch of sloppy as any casts inside here, but the implementation would need to change to match up with the new behaviors to align with Array.prototype.reduce anyhow. Obviously I'd try to avoid them in a final implementation.

}

const feature: Feature<Point> = {
type: "Feature",
geometry: { type: "Point", coordinates: [0, 0] },
properties: {},
};

const featureCollection: FeatureCollection<Point> = {
type: "FeatureCollection",
features: [feature],
};

const emptyFeatureCollection: FeatureCollection<Point> & { features: never[] } =
{
type: "FeatureCollection",
features: [],
};

// a FeatureCollection where we know that there is at least one feature (somehow)
const populatedFeatureCollection: {
type: "FeatureCollection";
features: [Feature<Point, GeoJsonProperties>];
} = {
type: "FeatureCollection",
features: [feature],
};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit is a little unfortunate. You cannot omit the explicit type definition here and use as const instead, because that marks the whole thing as readonly and breaks the typings of featureReduce. We should probably handle readonly inputs as well and just make the relevant things downstream of that also readonly.


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.
Expand Down
Loading