feat: extend agent-driven map workbench
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import type { FeatureCollection } from "geojson";
|
||||
import { GEOSERVER_WORKSPACE, MAP_URL } from "../../../lib/config";
|
||||
import { SOURCE_LAYERS, type WaterNetworkSourceId } from "./sources";
|
||||
|
||||
export const MAP_FEATURE_ID_FIELDS: Record<WaterNetworkSourceId, "id" | "sensor_id"> = {
|
||||
conduits: "id",
|
||||
junctions: "id",
|
||||
orifices: "id",
|
||||
outfalls: "id",
|
||||
pumps: "id",
|
||||
scada: "sensor_id"
|
||||
};
|
||||
|
||||
export const MAX_MAP_FEATURE_IDS = 100;
|
||||
export const MAX_MAP_FEATURE_ID_LENGTH = 128;
|
||||
export const MAX_CONDUIT_FEATURES = 5000;
|
||||
|
||||
export type MapFeatureQuery = {
|
||||
sourceId: WaterNetworkSourceId;
|
||||
featureIds?: string[];
|
||||
};
|
||||
|
||||
export type MapFeatureQueryValidation =
|
||||
| { ok: true; value: MapFeatureQuery }
|
||||
| { ok: false; code: "INVALID_REQUEST" | "UNSUPPORTED_SOURCE"; status: 400 | 422 };
|
||||
|
||||
export function parseMapFeatureQuery(body: unknown): MapFeatureQueryValidation {
|
||||
if (!body || typeof body !== "object") {
|
||||
return { ok: false, code: "INVALID_REQUEST", status: 400 };
|
||||
}
|
||||
|
||||
const { sourceId, featureIds } = body as { sourceId?: unknown; featureIds?: unknown };
|
||||
if (typeof sourceId !== "string" || !(sourceId in SOURCE_LAYERS)) {
|
||||
return { ok: false, code: "UNSUPPORTED_SOURCE", status: 422 };
|
||||
}
|
||||
|
||||
if (featureIds === undefined) {
|
||||
return sourceId === "conduits"
|
||||
? { ok: true, value: { sourceId } }
|
||||
: { ok: false, code: "INVALID_REQUEST", status: 400 };
|
||||
}
|
||||
|
||||
if (
|
||||
!Array.isArray(featureIds) ||
|
||||
featureIds.length < 1 ||
|
||||
featureIds.length > MAX_MAP_FEATURE_IDS ||
|
||||
!featureIds.every(
|
||||
(id) => typeof id === "string" && id.trim().length > 0 && id.trim().length <= MAX_MAP_FEATURE_ID_LENGTH
|
||||
)
|
||||
) {
|
||||
return { ok: false, code: "INVALID_REQUEST", status: 400 };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
value: { sourceId: sourceId as WaterNetworkSourceId, featureIds: featureIds.map((id) => id.trim()) }
|
||||
};
|
||||
}
|
||||
|
||||
export function escapeCqlLiteral(value: string) {
|
||||
return `'${value.replaceAll("'", "''")}'`;
|
||||
}
|
||||
|
||||
export function createMapFeatureWfsUrl(query: MapFeatureQuery) {
|
||||
const idField = MAP_FEATURE_ID_FIELDS[query.sourceId];
|
||||
const params = new URLSearchParams({
|
||||
service: "WFS",
|
||||
version: "2.0.0",
|
||||
request: "GetFeature",
|
||||
typeNames: `${GEOSERVER_WORKSPACE}:${SOURCE_LAYERS[query.sourceId]}`,
|
||||
outputFormat: "application/json",
|
||||
srsName: "EPSG:4326",
|
||||
count: String(query.featureIds ? MAX_MAP_FEATURE_IDS : MAX_CONDUIT_FEATURES)
|
||||
});
|
||||
|
||||
if (query.featureIds) {
|
||||
params.set("cql_filter", `${idField} IN (${query.featureIds.map(escapeCqlLiteral).join(",")})`);
|
||||
}
|
||||
|
||||
return new URL(`${MAP_URL}/${GEOSERVER_WORKSPACE}/ows?${params.toString()}`);
|
||||
}
|
||||
|
||||
export function normalizeMapFeatureCollection(value: unknown, maxFeatures: number): FeatureCollection | null {
|
||||
if (!value || typeof value !== "object") return null;
|
||||
const collection = value as FeatureCollection;
|
||||
if (collection.type !== "FeatureCollection" || !Array.isArray(collection.features)) return null;
|
||||
return { type: "FeatureCollection", features: collection.features.slice(0, maxFeatures) };
|
||||
}
|
||||
Reference in New Issue
Block a user