Files
next-tjwater-drainage-frontend/features/workbench/map/layers.ts
T

91 lines
10 KiB
TypeScript

import type { ExpressionSpecification, StyleSpecification } from "maplibre-gl";
import { DRAINAGE_LAYER_VISUALS } from "./map-layer-visuals";
import { MAP_STYLE_TOKENS } from "./map-colors";
import { SOURCE_LAYERS } from "./sources";
export const INTERACTIVE_HIT_LAYER_IDS = [
"conduits-hit", "junctions-hit", "orifices-hit", "pumps-hit", "outfalls-hit"
] as const;
const hoveredOpacity: ExpressionSpecification = [
"case",
["all", ["boolean", ["feature-state", "hovered"], false], ["!", ["boolean", ["feature-state", "selected"], false]]],
1,
0
];
const selectedOpacity: ExpressionSpecification = [
"case", ["boolean", ["feature-state", "selected"], false], 1, 0
];
const stateColor = (sourceColor: string): ExpressionSpecification => [
"case",
["in", ["downcase", ["to-string", ["coalesce", ["get", "status"], ""]]], ["literal", ["closed", "off", "inactive"]]],
MAP_STYLE_TOKENS.state.inactive,
sourceColor
];
const opacityTransition = { duration: 120, delay: 0 } as const;
type Layer = NonNullable<StyleSpecification["layers"]>[number];
type AuthoredLayer = { id: string; type: string; [key: string]: unknown };
const layers: AuthoredLayer[] = [];
// Business sources: white casing, then category-colored core.
layers.push(
{ id: "pipes-casing", type: "line", source: "conduits", "source-layer": SOURCE_LAYERS.conduits, minzoom: DRAINAGE_LAYER_VISUALS.conduits.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-width": DRAINAGE_LAYER_VISUALS.conduits.casingWidth, "line-opacity": MAP_STYLE_TOKENS.opacity.casing } },
{ id: "pipes-flow", type: "line", source: "conduits", "source-layer": SOURCE_LAYERS.conduits, minzoom: DRAINAGE_LAYER_VISUALS.conduits.minzoom, paint: { "line-color": stateColor(MAP_STYLE_TOKENS.asset.conduit), "line-width": DRAINAGE_LAYER_VISUALS.conduits.width, "line-opacity": MAP_STYLE_TOKENS.opacity.core } },
{ id: "junctions-halo", type: "circle", source: "junctions", "source-layer": SOURCE_LAYERS.junctions, minzoom: DRAINAGE_LAYER_VISUALS.junctions.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.casing, "circle-radius": DRAINAGE_LAYER_VISUALS.junctions.haloRadius, "circle-opacity": DRAINAGE_LAYER_VISUALS.junctions.haloOpacity } },
{ id: "junctions", type: "circle", source: "junctions", "source-layer": SOURCE_LAYERS.junctions, minzoom: DRAINAGE_LAYER_VISUALS.junctions.minzoom, paint: { "circle-color": stateColor(MAP_STYLE_TOKENS.asset.junction), "circle-radius": DRAINAGE_LAYER_VISUALS.junctions.radius, "circle-opacity": MAP_STYLE_TOKENS.opacity.core } },
{ id: "orifices-casing", type: "line", source: "orifices", "source-layer": SOURCE_LAYERS.orifices, minzoom: DRAINAGE_LAYER_VISUALS.orifices.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-width": DRAINAGE_LAYER_VISUALS.orifices.casingWidth, "line-opacity": 1 } },
{ id: "orifices", type: "line", source: "orifices", "source-layer": SOURCE_LAYERS.orifices, minzoom: DRAINAGE_LAYER_VISUALS.orifices.minzoom, paint: { "line-color": stateColor(MAP_STYLE_TOKENS.asset.orifice), "line-width": DRAINAGE_LAYER_VISUALS.orifices.width, "line-dasharray": [4, 2], "line-opacity": MAP_STYLE_TOKENS.opacity.core } },
{ id: "pumps-casing", type: "line", source: "pumps", "source-layer": SOURCE_LAYERS.pumps, minzoom: DRAINAGE_LAYER_VISUALS.pumps.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-width": DRAINAGE_LAYER_VISUALS.pumps.casingWidth, "line-opacity": 1 } },
{ id: "pumps", type: "line", source: "pumps", "source-layer": SOURCE_LAYERS.pumps, minzoom: DRAINAGE_LAYER_VISUALS.pumps.minzoom, paint: { "line-color": stateColor(MAP_STYLE_TOKENS.asset.pump), "line-width": DRAINAGE_LAYER_VISUALS.pumps.width, "line-dasharray": [6, 2, 1.5, 2], "line-opacity": MAP_STYLE_TOKENS.opacity.core } },
{ id: "outfalls-halo", type: "circle", source: "outfalls", "source-layer": SOURCE_LAYERS.outfalls, minzoom: DRAINAGE_LAYER_VISUALS.outfalls.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.casing, "circle-radius": DRAINAGE_LAYER_VISUALS.outfalls.haloRadius, "circle-opacity": 1 } },
{ id: "outfalls", type: "circle", source: "outfalls", "source-layer": SOURCE_LAYERS.outfalls, minzoom: DRAINAGE_LAYER_VISUALS.outfalls.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.casing, "circle-radius": DRAINAGE_LAYER_VISUALS.outfalls.radius, "circle-opacity": 1, "circle-stroke-color": stateColor(MAP_STYLE_TOKENS.asset.outfall), "circle-stroke-width": 2 } }
);
const lineInteractions = [
["pipes", "conduits", SOURCE_LAYERS.conduits, DRAINAGE_LAYER_VISUALS.conduits, MAP_STYLE_TOKENS.asset.conduit],
["orifices", "orifices", SOURCE_LAYERS.orifices, DRAINAGE_LAYER_VISUALS.orifices, MAP_STYLE_TOKENS.asset.orifice],
["pumps", "pumps", SOURCE_LAYERS.pumps, DRAINAGE_LAYER_VISUALS.pumps, MAP_STYLE_TOKENS.asset.pump]
] as const;
lineInteractions.forEach(([prefix, source, sourceLayer, visual, color]) => {
layers.push(
{ id: `${prefix}-hover-outline`, type: "line", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-width": visual.hoverOutlineWidth, "line-opacity": hoveredOpacity, "line-opacity-transition": opacityTransition } },
{ id: `${prefix}-hover`, type: "line", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "line-color": stateColor(color), "line-width": visual.hoverWidth, "line-opacity": hoveredOpacity, "line-opacity-transition": opacityTransition } },
{ id: `${prefix}-selected-outer`, type: "line", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-gap-width": visual.selectedWidth, "line-width": 4.5, "line-opacity": selectedOpacity, "line-opacity-transition": opacityTransition } },
{ id: `${prefix}-selected-outline`, type: "line", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.state.selected, "line-gap-width": visual.selectedWidth, "line-width": visual.selectedBorderWidth, "line-opacity": selectedOpacity, "line-opacity-transition": opacityTransition } },
{ id: `${prefix}-selected-separator`, type: "line", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.casing, "line-gap-width": visual.selectedWidth, "line-width": 1, "line-offset": -1.5, "line-opacity": selectedOpacity, "line-opacity-transition": opacityTransition } }
);
});
const pointInteractions = [
["junctions", "junctions", SOURCE_LAYERS.junctions, DRAINAGE_LAYER_VISUALS.junctions, MAP_STYLE_TOKENS.asset.junction],
["outfalls", "outfalls", SOURCE_LAYERS.outfalls, DRAINAGE_LAYER_VISUALS.outfalls, MAP_STYLE_TOKENS.asset.outfall]
] as const;
pointInteractions.forEach(([prefix, source, sourceLayer, visual, color]) => {
layers.push(
{ id: `${prefix}-hover`, type: "circle", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "circle-color": stateColor(color), "circle-radius": visual.hoverRadius, "circle-opacity": hoveredOpacity, "circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing, "circle-stroke-width": 2, "circle-opacity-transition": opacityTransition, "circle-stroke-opacity": hoveredOpacity, "circle-stroke-opacity-transition": opacityTransition } },
{ id: `${prefix}-selected-outer`, type: "circle", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.transparent, "circle-radius": visual.selectedRadius, "circle-opacity": selectedOpacity, "circle-stroke-color": MAP_STYLE_TOKENS.canvas.casing, "circle-stroke-width": 6, "circle-stroke-opacity": selectedOpacity, "circle-opacity-transition": opacityTransition, "circle-stroke-opacity-transition": opacityTransition } },
{ id: `${prefix}-selected`, type: "circle", source, "source-layer": sourceLayer, minzoom: visual.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.transparent, "circle-radius": visual.selectedRadius, "circle-opacity": selectedOpacity, "circle-stroke-color": MAP_STYLE_TOKENS.state.selected, "circle-stroke-width": 3, "circle-stroke-opacity": selectedOpacity, "circle-opacity-transition": opacityTransition, "circle-stroke-opacity-transition": opacityTransition } }
);
});
// Transparent hit layers stay last so pointer targeting is independent of paint order.
layers.push(
{ id: "conduits-hit", type: "line", source: "conduits", "source-layer": SOURCE_LAYERS.conduits, minzoom: DRAINAGE_LAYER_VISUALS.conduits.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.transparent, "line-width": DRAINAGE_LAYER_VISUALS.conduits.hitWidth, "line-opacity": 0 } },
{ id: "junctions-hit", type: "circle", source: "junctions", "source-layer": SOURCE_LAYERS.junctions, minzoom: DRAINAGE_LAYER_VISUALS.junctions.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.transparent, "circle-radius": DRAINAGE_LAYER_VISUALS.junctions.hitRadius, "circle-opacity": 0 } },
{ id: "orifices-hit", type: "line", source: "orifices", "source-layer": SOURCE_LAYERS.orifices, minzoom: DRAINAGE_LAYER_VISUALS.orifices.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.transparent, "line-width": DRAINAGE_LAYER_VISUALS.orifices.hitWidth, "line-opacity": 0 } },
{ id: "pumps-hit", type: "line", source: "pumps", "source-layer": SOURCE_LAYERS.pumps, minzoom: DRAINAGE_LAYER_VISUALS.pumps.minzoom, paint: { "line-color": MAP_STYLE_TOKENS.canvas.transparent, "line-width": DRAINAGE_LAYER_VISUALS.pumps.hitWidth, "line-opacity": 0 } },
{ id: "outfalls-hit", type: "circle", source: "outfalls", "source-layer": SOURCE_LAYERS.outfalls, minzoom: DRAINAGE_LAYER_VISUALS.outfalls.minzoom, paint: { "circle-color": MAP_STYLE_TOKENS.canvas.transparent, "circle-radius": DRAINAGE_LAYER_VISUALS.outfalls.hitRadius, "circle-opacity": 0 } }
);
export const waterNetworkLayers: StyleSpecification["layers"] = layers as Layer[];
const interactionIndex = layers.findIndex((layer) => layer.id === "pipes-hover-outline");
const hitIndex = layers.findIndex((layer) => layer.id === INTERACTIVE_HIT_LAYER_IDS[0]);
export const waterNetworkBusinessLayers = layers.slice(0, interactionIndex) as Layer[];
export const waterNetworkInteractionLayers = layers.slice(interactionIndex, hitIndex) as Layer[];
export const waterNetworkHitLayers = layers.slice(hitIndex) as Layer[];
export const WORKBENCH_INTERACTION_BEFORE_ID = "pipes-hover-outline";