@seasketch/geoprocessing
Version:
Geoprocessing and reporting framework for SeaSketch 2.0
79 lines • 3.75 kB
JavaScript
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