Skip to content

Commit

Permalink
Uce original clipToOceanEez
Browse files Browse the repository at this point in the history
  • Loading branch information
avmey committed Feb 4, 2025
1 parent 1d17dfa commit 6e6cce4
Showing 1 changed file with 91 additions and 42 deletions.
133 changes: 91 additions & 42 deletions src/functions/clipToOceanEez.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,109 @@
import {
ValidationError,
PreprocessingHandler,
VectorDataSource,
isPolygonFeature,
Feature,
FeatureClipOperation,
MultiPolygon,
Polygon,
PreprocessingHandler,
Sketch,
clipToPolygonFeatures,
ensureValidPolygon,
isVectorDatasource,
loadFgb,
MultiPolygon,
clip,
} from "@seasketch/geoprocessing";
import project from "../../project/projectClient.js";
import { bbox } from "@turf/turf";
import area from "@turf/area";
import bbox from "@turf/bbox";
import { featureCollection as fc } from "@turf/helpers";
import combine from "@turf/combine";
import flatten from "@turf/flatten";
import kinks from "@turf/kinks";

const MAX_SKETCH_SIZE = 1000000 * 1000 ** 2;

type OsmLandFeature = Feature<Polygon, { gid: number }>;
type EezLandUnion = Feature<Polygon, { gid: number; UNION: string }>;

// Defined at module level for potential caching/reuse by serverless process
const SubdividedOsmLandSource = new VectorDataSource<OsmLandFeature>(
"https://d3p1dsef9f0gjr.cloudfront.net/",
);
const SubdividedEezLandUnionSource = new VectorDataSource<EezLandUnion>(
"https://d3muy0hbwp5qkl.cloudfront.net",
);

export async function clipLand(feature: Feature<Polygon | MultiPolygon>) {
const landFeatures = await SubdividedOsmLandSource.fetchUnion(
bbox(feature),
"gid",
);
if (landFeatures.features.length === 0) return feature;
const combined = combine(landFeatures).features[0] as Feature<MultiPolygon>;
return combined ? clip(fc([feature, combined]), "difference") : feature;
}

export async function clipOutsideEez(
feature: Feature<Polygon | MultiPolygon>,
eezFilterByNames: string[] = [],
) {
let eezFeatures = await SubdividedEezLandUnionSource.fetch(bbox(feature));
// Optionally filter down to a single country/union EEZ boundary
if (eezFilterByNames.length > 0) {
eezFeatures = eezFeatures.filter((e) =>
eezFilterByNames.includes(e.properties.UNION),
);
}
if (eezFeatures.length === 0) return feature;
const combined = combine(fc(eezFeatures))
.features[0] as Feature<MultiPolygon>;
return clip(fc([feature, combined]), "intersection");
}

/**
* Preprocessor takes a Polygon feature/sketch and returns the portion that
* is in the ocean (not on land) and within one or more EEZ boundaries.
* Takes a Polygon feature and returns the portion that is in the ocean and within an EEZ boundary
* If results in multiple polygons then returns the largest
*/
export async function clipToOceanEez(
feature: Feature | Sketch,
feature: Feature,
eezFilterByNames?: string[],
): Promise<Feature> {
// throws if not valid with specific message
ensureValidPolygon(feature, {
minSize: 1,
enforceMinSize: false,
maxSize: 500_000 * 1000 ** 2, // Default 500,000 KM
enforceMaxSize: false,
});

const featureBox = bbox(feature);

const ds = project.getDatasourceById("nearshore_dissolved");
if (!isVectorDatasource(ds))
throw new Error(`Expected vector datasource for ${ds.datasourceId}`);
const url = project.getDatasourceUrl(ds);

// Keep portion of sketch within EEZ
const features: Feature<Polygon | MultiPolygon>[] = await loadFgb(
url,
featureBox,
);
if (!isPolygonFeature(feature)) {
throw new ValidationError("Input must be a polygon");
}

if (area(feature) > MAX_SKETCH_SIZE) {
throw new ValidationError(
"Please limit sketches to under 1,000,000 square km",
);
}

const kinkPoints = kinks(feature);
if (kinkPoints.features.length > 0) {
throw new ValidationError("Your sketch polygon crosses itself.");
}

const keepInsideEez: FeatureClipOperation = {
operation: "intersection",
clipFeatures: features,
};
let clipped = await clipLand(feature);
if (clipped) clipped = await clipOutsideEez(clipped, eezFilterByNames);

return clipToPolygonFeatures(feature, [keepInsideEez], {
ensurePolygon: true,
});
if (!clipped || area(clipped) === 0) {
throw new ValidationError("Sketch is outside of project boundaries");
} else {
if (clipped.geometry.type === "MultiPolygon") {
const flattened = flatten(clipped);
let biggest = [0, 0];
for (var i = 0; i < flattened.features.length; i++) {
const a = area(flattened.features[i]);
if (a > biggest[0]) {
biggest = [a, i];
}
}
return flattened.features[biggest[1]] as Feature<Polygon>;
} else {
return clipped;
}
}
}

export default new PreprocessingHandler(clipToOceanEez, {
title: "clipToOceanEez",
description: "Clips sketches to state waters",
description:
"Erases portion of sketch overlapping with land or extending into ocean outsize EEZ boundary",
timeout: 40,
requiresProperties: [],
memory: 4096,
});

0 comments on commit 6e6cce4

Please sign in to comment.