294 lines
7.8 KiB
TypeScript
294 lines
7.8 KiB
TypeScript
import type {
|
|
FilterSpecification,
|
|
GeoJSONSource,
|
|
GeoJSONSourceSpecification,
|
|
StyleSpecification
|
|
} from "maplibre-gl";
|
|
import type { FeatureCollection, Geometry, LineString, Point, Polygon } from "geojson";
|
|
import { createCircleCoordinates } from "./geo";
|
|
import { MAP_STYLE_TOKENS } from "./map-colors";
|
|
import { WORKBENCH_INTERACTION_BEFORE_ID } from "./layers";
|
|
|
|
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<Geometry, DrawingFeatureProperties>;
|
|
|
|
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": MAP_STYLE_TOKENS.tool.geometry,
|
|
"fill-opacity": 0.16
|
|
}
|
|
},
|
|
{
|
|
id: "workbench-drawing-polygon-outline",
|
|
type: "line",
|
|
source: DRAWING_SOURCE_IDS.features,
|
|
filter: ["==", ["geometry-type"], "Polygon"],
|
|
paint: {
|
|
"line-color": MAP_STYLE_TOKENS.tool.geometry,
|
|
"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": MAP_STYLE_TOKENS.tool.geometry,
|
|
"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": MAP_STYLE_TOKENS.tool.geometry,
|
|
"circle-radius": 6,
|
|
"circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing,
|
|
"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": MAP_STYLE_TOKENS.state.selected,
|
|
"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": MAP_STYLE_TOKENS.state.selected,
|
|
"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": MAP_STYLE_TOKENS.state.selected,
|
|
"circle-radius": 10,
|
|
"circle-opacity": 0.2,
|
|
"circle-stroke-color": MAP_STYLE_TOKENS.state.selected,
|
|
"circle-stroke-width": 2
|
|
}
|
|
},
|
|
{
|
|
id: "workbench-drawing-draft-polygon-fill",
|
|
type: "fill",
|
|
source: DRAWING_SOURCE_IDS.draft,
|
|
filter: ["==", ["geometry-type"], "Polygon"],
|
|
paint: {
|
|
"fill-color": MAP_STYLE_TOKENS.tool.geometry,
|
|
"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": MAP_STYLE_TOKENS.tool.geometry,
|
|
"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": MAP_STYLE_TOKENS.state.selected,
|
|
"circle-radius": 4,
|
|
"circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing,
|
|
"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<StyleSpecification["layers"]>[number], beforeId?: string) => 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, WORKBENCH_INTERACTION_BEFORE_ID);
|
|
}
|
|
});
|
|
}
|
|
|
|
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()
|
|
};
|
|
}
|