import type { FilterSpecification, GeoJSONSource, GeoJSONSourceSpecification, StyleSpecification } from "maplibre-gl"; import type { FeatureCollection, Geometry, LineString, Point, Polygon } from "geojson"; import { createCircleCoordinates } from "./geo"; export const DRAWING_SOURCE_IDS = { features: "workbench-drawing-features", draft: "workbench-drawing-draft" } as const; export const DRAWING_INTERACTIVE_LAYER_IDS = [ "workbench-drawing-selected-polygon-outline", "workbench-drawing-selected-line", "workbench-drawing-selected-point", "workbench-drawing-polygon-fill", "workbench-drawing-polygon-outline", "workbench-drawing-line", "workbench-drawing-point" ]; export type DrawingFeatureProperties = { id: string; label: string; kind: "point" | "line" | "polygon" | "circle"; createdAt: string; }; export type DrawingFeatureCollection = FeatureCollection; export const emptyDrawingFeatureCollection: DrawingFeatureCollection = { type: "FeatureCollection", features: [] }; export const drawingSources = { features: { type: "geojson", data: emptyDrawingFeatureCollection } satisfies GeoJSONSourceSpecification, draft: { type: "geojson", data: emptyDrawingFeatureCollection } satisfies GeoJSONSourceSpecification }; export const drawingLayers: StyleSpecification["layers"] = [ { id: "workbench-drawing-polygon-fill", type: "fill", source: DRAWING_SOURCE_IDS.features, filter: ["==", ["geometry-type"], "Polygon"], paint: { "fill-color": "#2563eb", "fill-opacity": 0.16 } }, { id: "workbench-drawing-polygon-outline", type: "line", source: DRAWING_SOURCE_IDS.features, filter: ["==", ["geometry-type"], "Polygon"], paint: { "line-color": "#2563eb", "line-width": 2, "line-opacity": 0.82 } }, { id: "workbench-drawing-line", type: "line", source: DRAWING_SOURCE_IDS.features, filter: ["==", ["geometry-type"], "LineString"], paint: { "line-color": "#2563eb", "line-width": 3, "line-opacity": 0.88 } }, { id: "workbench-drawing-point", type: "circle", source: DRAWING_SOURCE_IDS.features, filter: ["==", ["geometry-type"], "Point"], paint: { "circle-color": "#2563eb", "circle-radius": 6, "circle-stroke-color": "#ffffff", "circle-stroke-width": 2 } }, { id: "workbench-drawing-selected-polygon-outline", type: "line", source: DRAWING_SOURCE_IDS.features, filter: ["all", ["==", ["geometry-type"], "Polygon"], ["==", ["get", "id"], ""]], paint: { "line-color": "#ff7a45", "line-width": 4, "line-opacity": 0.86 } }, { id: "workbench-drawing-selected-line", type: "line", source: DRAWING_SOURCE_IDS.features, filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "id"], ""]], paint: { "line-color": "#ff7a45", "line-width": 6, "line-opacity": 0.72 } }, { id: "workbench-drawing-selected-point", type: "circle", source: DRAWING_SOURCE_IDS.features, filter: ["all", ["==", ["geometry-type"], "Point"], ["==", ["get", "id"], ""]], paint: { "circle-color": "#ff7a45", "circle-radius": 10, "circle-opacity": 0.2, "circle-stroke-color": "#ff7a45", "circle-stroke-width": 2 } }, { id: "workbench-drawing-draft-polygon-fill", type: "fill", source: DRAWING_SOURCE_IDS.draft, filter: ["==", ["geometry-type"], "Polygon"], paint: { "fill-color": "#ff7a45", "fill-opacity": 0.12 } }, { id: "workbench-drawing-draft-line", type: "line", source: DRAWING_SOURCE_IDS.draft, filter: ["any", ["==", ["geometry-type"], "LineString"], ["==", ["geometry-type"], "Polygon"]], paint: { "line-color": "#ff7a45", "line-width": 2, "line-dasharray": [2, 2], "line-opacity": 0.9 } }, { id: "workbench-drawing-draft-vertex", type: "circle", source: DRAWING_SOURCE_IDS.draft, filter: ["==", ["geometry-type"], "Point"], paint: { "circle-color": "#ff7a45", "circle-radius": 4, "circle-stroke-color": "#ffffff", "circle-stroke-width": 1.5 } } ]; export function ensureDrawingLayers(map: { getSource: (id: string) => unknown; addSource: (id: string, source: GeoJSONSourceSpecification) => void; getLayer: (id: string) => unknown; addLayer: (layer: NonNullable[number]) => void; }) { if (!map.getSource(DRAWING_SOURCE_IDS.features)) { map.addSource(DRAWING_SOURCE_IDS.features, drawingSources.features); } if (!map.getSource(DRAWING_SOURCE_IDS.draft)) { map.addSource(DRAWING_SOURCE_IDS.draft, drawingSources.draft); } drawingLayers.forEach((layer) => { if (!map.getLayer(layer.id)) { map.addLayer(layer); } }); } export function setDrawingSourceData( map: { getSource: (id: string) => unknown }, sourceId: string, data: DrawingFeatureCollection ) { const source = map.getSource(sourceId) as GeoJSONSource | undefined; source?.setData(data); } export function setSelectedDrawingFeatureId( map: { getLayer: (id: string) => unknown; setFilter: (id: string, filter?: FilterSpecification) => void }, featureId: string | null ) { const id = featureId ?? ""; [ ["workbench-drawing-selected-polygon-outline", "Polygon"], ["workbench-drawing-selected-line", "LineString"], ["workbench-drawing-selected-point", "Point"] ].forEach(([layerId, geometryType]) => { if (map.getLayer(layerId)) { map.setFilter(layerId, ["all", ["==", ["geometry-type"], geometryType], ["==", ["get", "id"], id]]); } }); } export function createPointFeature( id: string, coordinates: [number, number], label: string ): DrawingFeatureCollection["features"][number] { return { type: "Feature", id, properties: createDrawingProperties(id, label, "point"), geometry: { type: "Point", coordinates } satisfies Point }; } export function createLineFeature( id: string, coordinates: [number, number][], label: string ): DrawingFeatureCollection["features"][number] { return { type: "Feature", id, properties: createDrawingProperties(id, label, "line"), geometry: { type: "LineString", coordinates } satisfies LineString }; } export function createPolygonFeature( id: string, coordinates: [number, number][], label: string ): DrawingFeatureCollection["features"][number] { return { type: "Feature", id, properties: createDrawingProperties(id, label, "polygon"), geometry: { type: "Polygon", coordinates: [[...coordinates, coordinates[0]]] } satisfies Polygon }; } export function createCircleFeature( id: string, center: [number, number], radiusMeters: number, label: string ): DrawingFeatureCollection["features"][number] { const coordinates = createCircleCoordinates(center, radiusMeters); return { type: "Feature", id, properties: createDrawingProperties(id, label, "circle"), geometry: { type: "Polygon", coordinates: [coordinates] } satisfies Polygon }; } function createDrawingProperties( id: string, label: string, kind: DrawingFeatureProperties["kind"] ): DrawingFeatureProperties { return { id, label, kind, createdAt: new Date().toISOString() }; }