Skip to content

Commit

Permalink
Adds overlapRasterGroupMetrics function (#270)
Browse files Browse the repository at this point in the history
* Adds overlapRasterGroupMetrics function

* Tests overlapGroupMetrics can recieve feature[]
  • Loading branch information
avmey authored and twelch committed Jun 25, 2024
1 parent adc3ae1 commit 48b2982
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 13 deletions.
153 changes: 147 additions & 6 deletions packages/geoprocessing/src/toolbox/overlapGroupMetrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
* @group unit
*/

import { overlapAreaGroupMetrics } from "./overlapGroupMetrics";
import { SketchCollection, Polygon, Metric } from "../types";
import {
overlapAreaGroupMetrics,
overlapFeaturesGroupMetrics,
overlapRasterGroupMetrics,
} from "./overlapGroupMetrics";
import { SketchCollection, Polygon, Metric, Sketch, Feature } from "../types";
import parseGeoraster from "georaster";
import { toFeaturePolygonArray } from "../helpers";

const sketch: SketchCollection<Polygon> = {
type: "FeatureCollection",
Expand Down Expand Up @@ -136,16 +142,16 @@ const areaMetrics: Metric[] = [
];
const STUDY_REGION_AREA_SQ_METERS = 1_000_000_000_000;

const metricToLevel = (sketchMetric: Metric) => {
return sketchIdToGroupId[sketchMetric.sketchId!];
};

describe("overlapAreaGroupMetrics", () => {
test("function is present", () => {
expect(typeof overlapAreaGroupMetrics).toBe("function");
});

test("overlapAreaGroupMetrics - protection level", async () => {
const metricToLevel = (sketchMetric: Metric) => {
return sketchIdToGroupId[sketchMetric.sketchId!];
};

const metrics = await overlapAreaGroupMetrics({
metricId: "areaOverlap",
groupIds: protectionLevels,
Expand Down Expand Up @@ -197,4 +203,139 @@ describe("overlapAreaGroupMetrics", () => {
}
});
});

test("function is present", () => {
expect(typeof overlapFeaturesGroupMetrics).toBe("function");
});

test("overlapFeaturesGroupMetrics", async () => {
const featMetrics: Metric[] = [
{
metricId: "test",
value: 46020431.777366,
classId: "world",
groupId: null,
geographyId: null,
sketchId: "62055aac9557604f3e5f6d3e",
extra: { sketchName: "VitoriaNorth" },
},
{
metricId: "test",
value: 10335615.29727,
classId: "world",
groupId: null,
geographyId: null,
sketchId: "62055ac19557604f3e5f6d43",
extra: { sketchName: "VitoriaSouth" },
},
{
metricId: "test",
value: 56356047.074636,
classId: "world",
groupId: null,
geographyId: null,
sketchId: "62055ac79557604f3e5f6d44",
extra: { sketchName: "Vitoria", isCollection: true },
},
];
const features = toFeaturePolygonArray(sketch);
const featuresByClass: Record<string, Feature<Polygon>[]> = {
world: features as Feature<Polygon>[],
};

const metrics = await overlapFeaturesGroupMetrics({
metricId: "featuresOverlap",
groupIds: protectionLevels,
sketch,
metricToGroup: metricToLevel,
metrics: featMetrics,
featuresByClass: featuresByClass,
});

expect(metrics.length).toEqual(protectionLevels.length);
});

test("function is present", () => {
expect(typeof overlapRasterGroupMetrics).toBe("function");
});

test("overlapRasterGroupMetrics", async () => {
const raster = await parseGeoraster(
[
[
[1, 1],
[1, 1],
],
],
{
noDataValue: 0,
projection: 4326,
xmin: 0, // left
ymax: 20, // top
pixelWidth: 10,
pixelHeight: 10,
}
);

const wholePoly: Sketch<Polygon> = {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[2, 2],
[2, 20],
[20, 20],
[20, 2],
[2, 2],
],
],
},
properties: {
id: "62055aac9557604f3e5f6d3e",
name: "VitoriaNorth",
updatedAt: "2022-02-10T18:34:52.380Z",
createdAt: "2022-02-10T18:34:20.755Z",
sketchClassId: "5f46dd53017aa0388f5f87c5",
isCollection: false,
userAttributes: [],
},
};

const rasterMetrics = [
{
metricId: "rasterOverlap",
value: 4,
classId: "eez",
groupId: null,
geographyId: null,
sketchId: "62055aac9557604f3e5f6d3e",
extra: { sketchName: "VitoriaNorth" },
},
];

const metrics = await overlapRasterGroupMetrics({
metricId: "rasterOverlap",
groupIds: protectionLevels,
sketch: wholePoly,
metricToGroup: metricToLevel,
metrics: rasterMetrics,
featuresByClass: { eez: raster },
});

// Test collection level metrics. Expect one metric per protection level
expect(metrics.length).toEqual(protectionLevels.length);

// Only "High Protection" should have a value > 0
protectionLevels.forEach((level) => {
const curLevelMetrics = metrics.filter((m) => m.groupId === level);
expect(curLevelMetrics.length).toBe(1);
const curLevelMetric = curLevelMetrics[0];
if (curLevelMetric.groupId === "Highly Protected Area") {
expect(curLevelMetric.value).toBeGreaterThan(0);
} else {
expect(curLevelMetric.value).toBe(0);
}
});
});
});
61 changes: 54 additions & 7 deletions packages/geoprocessing/src/toolbox/overlapGroupMetrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
MultiPolygon,
SketchCollection,
Metric,
Georaster,
} from "../types";
import {
genSampleSketchCollection,
Expand All @@ -13,20 +14,61 @@ import {
isSketchCollection,
groupBy,
clip,
isPolygonFeatureArray,
} from "../helpers";
import { createMetric } from "../metrics";
import { createMetric, firstMatchingMetric } from "../metrics";
import { overlapFeatures } from "./overlapFeatures";
import { overlapArea } from "./overlapArea";
import flatten from "@turf/flatten";
import { featureCollection } from "@turf/helpers";
import cloneDeep from "lodash/cloneDeep";
import { rasterMetrics } from "./rasterMetrics";

type OverlapGroupOperation = (
metricId: string,
features: Feature<Polygon>[],
features: Feature<Polygon>[] | Georaster,
sc: SketchCollection<Polygon>
) => Promise<number>;

/**
* Generate overlap group metrics using rasterMetrics operation
*/
export async function overlapRasterGroupMetrics(options: {
/** Caller-provided metric ID */
metricId: string;
/** Group identifiers - will generate group metric for each, even if result in zero value, so pre-filter if want to limit */
groupIds: string[];
/** Sketch - single or collection */
sketch: Sketch<Polygon> | SketchCollection<Polygon>;
/** Function that given sketch metric and group name, returns true if sketch is in the group, otherwise false */
metricToGroup: (sketchMetric: Metric) => string;
/** The metrics to group */
metrics: Metric[];
/** Raster to overlap, keyed by class ID */
featuresByClass: Record<string, Georaster>;
/** only generate metrics for groups that sketches match to, rather than all */
onlyPresentGroups?: boolean;
}): Promise<Metric[]> {
return overlapGroupMetrics({
...options,
operation: async (
metricId: string,
features: Georaster | Feature<Polygon>[],
sc: SketchCollection<Polygon>
) => {
if (isPolygonFeatureArray(features)) throw new Error(`Expected raster`);
const overallGroupMetrics = await rasterMetrics(features, {
metricId: metricId,
feature: sc,
});
return firstMatchingMetric(
overallGroupMetrics,
(m) => !!m.extra?.isCollection
).value;
},
});
}

/**
* Generate overlap group metrics using overlapFeatures operation
*/
Expand All @@ -50,9 +92,12 @@ export async function overlapFeaturesGroupMetrics(options: {
...options,
operation: async (
metricId: string,
features: Feature<Polygon>[],
features: Feature<Polygon>[] | Georaster,
sc: SketchCollection<Polygon>
) => {
if (!isPolygonFeatureArray(features))
throw new Error(`Expected feature array`);

const overallGroupMetrics = await overlapFeatures(
metricId,
features,
Expand Down Expand Up @@ -91,7 +136,7 @@ export async function overlapAreaGroupMetrics(options: {
featuresByClass: { [options.classId]: [] },
operation: async (
metricId: string,
features: Feature<Polygon>[],
features: Feature<Polygon>[] | Georaster,
sc: SketchCollection<Polygon>
) => {
// Calculate just the overall area sum for group
Expand Down Expand Up @@ -130,7 +175,9 @@ export async function overlapGroupMetrics(options: {
/** The metrics to group */
metrics: Metric[];
/** features to overlap, keyed by class ID, use empty array if overlapArea operation */
featuresByClass: Record<string, Feature<Polygon>[]>;
featuresByClass:
| Record<string, Feature<Polygon>[]>
| Record<string, Georaster>;
/** overlap operation, defaults to overlapFeatures */
operation: OverlapGroupOperation;
/** only generate metrics for groups that sketches match to, rather than all groupIds */
Expand Down Expand Up @@ -226,7 +273,7 @@ const getClassGroupMetrics = async (options: {
groups: string[];
groupId: string;
metricId: string;
features: Feature<Polygon>[];
features: Feature<Polygon>[] | Georaster;
operation: OverlapGroupOperation;
}): Promise<Metric[]> => {
const {
Expand Down Expand Up @@ -338,7 +385,7 @@ const getReducedGroupAreaOverlap = async (options: {
/** sketches in other groups that take precedence and overlap must be removed. */
higherGroupSketches: Sketch<Polygon>[];
/** polygon features to overlap with */
features: Feature<Polygon>[];
features: Feature<Polygon>[] | Georaster;
operation: OverlapGroupOperation;
}) => {
const { metricId, groupSketches, higherGroupSketches, features, operation } =
Expand Down

0 comments on commit 48b2982

Please sign in to comment.