feat(map): add five-source drainage styling
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getResponsiveWorkbenchPadding
|
||||
} from "../map/camera";
|
||||
import { waterNetworkLayers } from "../map/layers";
|
||||
import { MAP_MAX_ZOOM } from "../map/map-layer-visuals";
|
||||
import { setSimulationLayersVisibility } from "../map/simulation-layers";
|
||||
import {
|
||||
createBaseStyle,
|
||||
@@ -36,6 +37,7 @@ type UseWorkbenchMapOptions = {
|
||||
rightPanelOpen: boolean;
|
||||
impactVisible: boolean;
|
||||
onSelectFeature: (feature: DetailFeature) => void;
|
||||
selectedFeature: DetailFeature | null;
|
||||
};
|
||||
|
||||
type UseWorkbenchMapResult = {
|
||||
@@ -64,8 +66,11 @@ const SOURCE_STATUS_LABELS: Record<string, string> = {
|
||||
const SOURCE_GROUP_BY_ID: Record<string, string> = {
|
||||
"mapbox-light": "mapbox-base",
|
||||
"mapbox-satellite": "mapbox-base",
|
||||
pipes: "geoserver-mvt",
|
||||
conduits: "geoserver-mvt",
|
||||
junctions: "geoserver-mvt",
|
||||
orifices: "geoserver-mvt",
|
||||
outfalls: "geoserver-mvt",
|
||||
pumps: "geoserver-mvt",
|
||||
[SIMULATION_SOURCE_IDS.impactArea]: "annotation-source",
|
||||
[SIMULATION_SOURCE_IDS.annotations]: "annotation-source"
|
||||
};
|
||||
@@ -75,7 +80,8 @@ export function useWorkbenchMap({
|
||||
leftPanelOpen,
|
||||
rightPanelOpen,
|
||||
impactVisible,
|
||||
onSelectFeature
|
||||
onSelectFeature,
|
||||
selectedFeature
|
||||
}: UseWorkbenchMapOptions): UseWorkbenchMapResult {
|
||||
const mapRef = useRef<MapLibreMap | null>(null);
|
||||
const impactVisibleRef = useRef(impactVisible);
|
||||
@@ -104,6 +110,7 @@ export function useWorkbenchMap({
|
||||
style: createBaseStyle(mapboxToken),
|
||||
pitch: 0,
|
||||
bearing: 0,
|
||||
maxZoom: MAP_MAX_ZOOM,
|
||||
preserveDrawingBuffer: true,
|
||||
attributionControl: false
|
||||
});
|
||||
@@ -113,8 +120,11 @@ export function useWorkbenchMap({
|
||||
map.on("load", () => {
|
||||
map.resize();
|
||||
const sources = createWaterNetworkSources();
|
||||
map.addSource("pipes", sources.pipes);
|
||||
map.addSource("conduits", sources.conduits);
|
||||
map.addSource("junctions", sources.junctions);
|
||||
map.addSource("orifices", sources.orifices);
|
||||
map.addSource("outfalls", sources.outfalls);
|
||||
map.addSource("pumps", sources.pumps);
|
||||
map.addSource(SIMULATION_SOURCE_IDS.impactArea, simulationSources.impactArea);
|
||||
map.addSource(SIMULATION_SOURCE_IDS.annotations, simulationSources.annotations);
|
||||
waterNetworkLayers.forEach((layer) => map.addLayer(layer));
|
||||
@@ -165,7 +175,8 @@ export function useWorkbenchMap({
|
||||
useMapInteractions({
|
||||
mapRef,
|
||||
mapReady,
|
||||
onSelectFeature
|
||||
onSelectFeature,
|
||||
selectedFeature
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -278,7 +289,7 @@ function getSourceStatusMessage(sourceGroupId: string, status: WorkbenchSourceSt
|
||||
}
|
||||
|
||||
if (sourceGroupId === "geoserver-mvt") {
|
||||
return status === "online" ? "管线和节点矢量瓦片已加载。" : "管线或节点瓦片请求中断,当前视图可能不完整。";
|
||||
return status === "online" ? "排水管网矢量瓦片已加载。" : "排水管网瓦片请求中断,当前视图可能不完整。";
|
||||
}
|
||||
|
||||
if (sourceGroupId === "annotation-source") {
|
||||
|
||||
Reference in New Issue
Block a user