UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

109 lines (97 loc) 3.29 kB
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, });