feat(map): add five-source drainage styling

This commit is contained in:
2026-07-10 18:45:43 +08:00
parent 2258177726
commit 513c403017
19 changed files with 1303 additions and 182 deletions
@@ -4,21 +4,42 @@ import type { Map as MapLibreMap, MapLayerMouseEvent } from "maplibre-gl";
import type { RefObject } from "react";
import { useEffect } from "react";
import type { DetailFeature } from "../types";
import { getFeatureId, toDetailFeature } from "../map/feature-adapter";
import { SOURCE_LAYERS } from "../map/sources";
import {
getFeatureId,
getWaterNetworkSourceId,
toDetailFeature
} from "../map/feature-adapter";
import { INTERACTIVE_HIT_LAYER_IDS } from "../map/layers";
import type { WaterNetworkSourceId } from "../map/sources";
const INTERACTIVE_LAYER_IDS = ["pipes-flow", "junctions"];
const HOVER_LAYER_IDS: Record<WaterNetworkSourceId, readonly string[]> = {
conduits: ["pipes-hover-outline", "pipes-hover"],
junctions: ["junctions-hover"],
orifices: ["orifices-hover-outline", "orifices-hover"],
pumps: ["pumps-hover-outline", "pumps-hover"],
outfalls: ["outfalls-hover"]
};
const SELECTED_LAYER_IDS: Record<WaterNetworkSourceId, readonly string[]> = {
conduits: ["pipes-selected-halo", "pipes-selected-outline", "pipes-selected"],
junctions: ["junctions-selected-halo", "junctions-selected"],
orifices: ["orifices-selected-halo", "orifices-selected-outline", "orifices-selected"],
pumps: ["pumps-selected-halo", "pumps-selected-outline", "pumps-selected"],
outfalls: ["outfalls-selected-halo", "outfalls-selected"]
};
type UseMapInteractionsOptions = {
mapRef: RefObject<MapLibreMap | null>;
mapReady: boolean;
onSelectFeature: (feature: DetailFeature) => void;
selectedFeature: DetailFeature | null;
};
export function useMapInteractions({
mapRef,
mapReady,
onSelectFeature
onSelectFeature,
selectedFeature
}: UseMapInteractionsOptions) {
useEffect(() => {
const map = mapRef.current;
@@ -27,23 +48,47 @@ export function useMapInteractions({
}
const activeMap = map;
let hoveredFeature: { id: string; sourceId: WaterNetworkSourceId } | null = null;
function clearHoveredFeature() {
if (!hoveredFeature) {
return;
}
HOVER_LAYER_IDS[hoveredFeature.sourceId].forEach((layerId) => {
activeMap.setFilter(layerId, ["==", ["get", "id"], ""]);
});
hoveredFeature = null;
}
function handleMouseMove(event: MapLayerMouseEvent) {
activeMap.getCanvas().style.cursor = "pointer";
const feature = event.features?.[0];
if (!feature) {
return;
}
activeMap.getCanvas().style.cursor = "pointer";
const id = getFeatureId(feature);
const layerId = feature.sourceLayer === SOURCE_LAYERS.pipes ? "pipes-hover" : "junctions-hover";
activeMap.setFilter(layerId, ["==", ["get", "id"], id]);
const sourceId = getWaterNetworkSourceId(feature);
if (!id || !sourceId) {
clearHoveredFeature();
return;
}
if (hoveredFeature?.id === id && hoveredFeature.sourceId === sourceId) {
return;
}
clearHoveredFeature();
HOVER_LAYER_IDS[sourceId].forEach((layerId) => {
activeMap.setFilter(layerId, ["==", ["get", "id"], id]);
});
hoveredFeature = { id, sourceId };
}
function handleMouseLeave() {
activeMap.getCanvas().style.cursor = "";
activeMap.setFilter("pipes-hover", ["==", ["get", "id"], ""]);
activeMap.setFilter("junctions-hover", ["==", ["get", "id"], ""]);
clearHoveredFeature();
}
function handleClick(event: MapLayerMouseEvent) {
@@ -53,14 +98,33 @@ export function useMapInteractions({
}
}
activeMap.on("mousemove", INTERACTIVE_LAYER_IDS, handleMouseMove);
activeMap.on("mouseleave", INTERACTIVE_LAYER_IDS, handleMouseLeave);
activeMap.on("click", INTERACTIVE_LAYER_IDS, handleClick);
activeMap.on("mousemove", INTERACTIVE_HIT_LAYER_IDS, handleMouseMove);
activeMap.on("mouseleave", INTERACTIVE_HIT_LAYER_IDS, handleMouseLeave);
activeMap.on("click", INTERACTIVE_HIT_LAYER_IDS, handleClick);
return () => {
activeMap.off("mousemove", INTERACTIVE_LAYER_IDS, handleMouseMove);
activeMap.off("mouseleave", INTERACTIVE_LAYER_IDS, handleMouseLeave);
activeMap.off("click", INTERACTIVE_LAYER_IDS, handleClick);
activeMap.off("mousemove", INTERACTIVE_HIT_LAYER_IDS, handleMouseMove);
activeMap.off("mouseleave", INTERACTIVE_HIT_LAYER_IDS, handleMouseLeave);
activeMap.off("click", INTERACTIVE_HIT_LAYER_IDS, handleClick);
};
}, [mapRef, mapReady, onSelectFeature]);
useEffect(() => {
const map = mapRef.current;
if (!mapReady || !map) {
return;
}
Object.values(SELECTED_LAYER_IDS).flat().forEach((layerId) => {
map.setFilter(layerId, ["==", ["get", "id"], ""]);
});
if (!selectedFeature?.id) {
return;
}
SELECTED_LAYER_IDS[selectedFeature.layer].forEach((layerId) => {
map.setFilter(layerId, ["==", ["get", "id"], selectedFeature.id]);
});
}, [mapReady, mapRef, selectedFeature]);
}