UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

79 lines 3.75 kB
import polygonClipping from "polygon-clipping"; import { multiPolygon, polygon, geomEach, getGeom, featureCollection, } from "@turf/turf"; import { ValidationError } from "../types/index.js"; import { chunk } from "../helpers/chunk.js"; /** * Performs one of 4 different clip operations on features * @param features - FeatureCollection of Polygons or MultiPolygons. First feature is the subject, the rest are the clippers * @param operation - one of "union", "intersection", "xor", "difference" * @param options - optional properties to set on the resulting feature * @returns clipped Feature of Polygon or MultiPolygon */ export function clip(features, operation, options = {}) { if (!features || !features.features || features.features.length === 0) throw new ValidationError("Missing or empty features for clip"); const coords = []; geomEach(features, (geom) => { coords.push(geom.coordinates); }); //@ts-expect-error type mismatch const clipped = polygonClipping[operation](coords[0], ...coords.slice(1)); if (clipped.length === 0) return null; if (clipped.length === 1) return polygon(clipped[0], options.properties); return multiPolygon(clipped, options.properties); } /** * Performs clip after first merging features2 coords into a single multipolygon. * Avoids errors in underlying clipping library when too many features in features2 * @param feature1 polygon or multipolygon to clip * @param features2 collection of polygons or multipolygons to clip feature1 against * @param operation one of "union", "intersection", "xor", "difference" * @param options.properties properties to set on the resulting feature * @returns polygon or multipolygon feature result from clip operation, if no overlap then returns null */ export function clipMultiMerge(feature1, features2, operation, options = {}) { if (!feature1 || !features2 || !features2.features || features2.features.length === 0) throw new ValidationError("Missing or empty features for clip"); const geom1 = getGeom(feature1); // Combine features2 into one multipoly coordinate array so that it is operated on in one go const coords2 = (() => { return features2.features.reduce((acc, poly) => { if (poly.geometry.type === "Polygon") { return [...acc, poly.geometry.coordinates]; } else { return [...acc, ...poly.geometry.coordinates]; } }, []); })(); const result = polygonClipping[operation](geom1.coordinates, coords2); if (result.length === 0) return null; if (result.length === 1) return polygon(result[0], options.properties); return multiPolygon(result, options.properties); } /** * Calculates area overlap between a feature A and a feature array B. * Intersection is done in chunks on featuresB to avoid errors due to too many features * @param featureA single feature to intersect with featuresB * @param featuresB array of features * @param chunkSize Size of array to split featuresB into, avoids intersect failure due to large array) * @returns intersection of featureA with featuresB */ export const intersectInChunks = (featureA, featuresB, chunkSize) => { // chunk to avoid blowing up intersect const chunks = chunk(featuresB, chunkSize || 5000); // intersect chunks and flatten into single result return chunks.flatMap((curChunk) => { // MultiMerge will merge curChunk FC into a single multipolygon before intersection const rem = clipMultiMerge(featureA, featureCollection(curChunk), "intersection"); return rem || []; }); }; //# sourceMappingURL=clip.js.map