@seasketch/geoprocessing
Version:
Geoprocessing and reporting framework for SeaSketch 2.0
109 lines (97 loc) • 3.29 kB
text/typescript
import {
Sketch,
Feature,
GeoprocessingHandler,
Metric,
Polygon,
ReportResult,
SketchCollection,
overlapFeatures,
rekeyMetrics,
sortMetrics,
isPolygonFeatureArray,
getFirstFromParam,
DefaultExtraParams,
splitSketchAntimeridian,
isVectorDatasource,
getFeatures,
} from "@seasketch/geoprocessing";
import { bbox } from "@turf/turf";
import project from "../../project/projectClient.js";
import { clipToGeography } from "../util/clipToGeography.js";
const metricGroup = project.getMetricGroup("boundaryAreaOverlap");
export async function boundaryAreaOverlap(
sketch: Sketch<Polygon> | SketchCollection<Polygon>,
extraParams: DefaultExtraParams = {},
): Promise<ReportResult> {
// Use caller-provided geographyId if provided
const geographyId = getFirstFromParam("geographyIds", extraParams);
// Get geography features, falling back to geography assigned to default-boundary group
const curGeography = project.getGeographyById(geographyId, {
fallbackGroup: "default-boundary",
});
// Support sketches crossing antimeridian
const splitSketch = splitSketchAntimeridian(sketch);
// Clip to portion of sketch within current geography
const clippedSketch = await clipToGeography(splitSketch, curGeography);
// Get bounding box of sketch remainder
const sketchBox = clippedSketch.bbox || bbox(clippedSketch);
// Fetch boundary features indexed by classId
const polysByBoundary = (
await Promise.all(
metricGroup.classes.map(async (curClass) => {
if (!curClass.datasourceId) {
throw new Error(`Missing datasourceId ${curClass.classId}`);
}
const ds = project.getDatasourceById(curClass.datasourceId);
if (!isVectorDatasource(ds)) {
throw new Error(`Expected vector datasource for ${ds.datasourceId}`);
}
// Fetch datasource features overlapping with sketch remainder
const url = project.getDatasourceUrl(ds);
const polys = await getFeatures(ds, url, {
bbox: sketchBox,
});
if (!isPolygonFeatureArray(polys)) {
throw new Error("Expected array of Polygon features");
}
return polys;
}),
)
).reduce<Record<string, Feature<Polygon>[]>>((acc, polys, classIndex) => {
return {
...acc,
[metricGroup.classes[classIndex].classId]: polys,
};
}, {});
const metrics: Metric[] = // calculate area overlap metrics for each class
(
await Promise.all(
metricGroup.classes.map(async (curClass) => {
const overlapResult = await overlapFeatures(
metricGroup.metricId,
polysByBoundary[curClass.classId],
clippedSketch,
);
return overlapResult.map(
(metric): Metric => ({
...metric,
classId: curClass.classId,
geographyId: curGeography.geographyId,
}),
);
}),
)
).flat();
return {
metrics: sortMetrics(rekeyMetrics(metrics)),
};
}
export default new GeoprocessingHandler(boundaryAreaOverlap, {
title: "boundaryAreaOverlap",
description: "Calculate sketch overlap with boundary polygons",
executionMode: "async",
timeout: 40,
requiresProperties: [],
memory: 10_240,
});