feat: extend supply workbench demo and map navigation

This commit is contained in:
2026-09-23 13:19:30 +08:00
parent bebcf2a06c
commit 1752b9ef84
45 changed files with 2137 additions and 449 deletions
+184 -49
View File
@@ -11,11 +11,7 @@ import {
SUPPLY_LAYER_CATALOG,
type WaterNetworkSourceId
} from "./sources";
import {
SCADA_ICON_PATHS,
SCADA_LAYER_CONTROL_ITEMS,
SCADA_LAYER_GROUPS
} from "./scada";
import { SCADA_ICON_PATHS, SCADA_LAYER_CONTROL_ITEMS, SCADA_LAYER_GROUPS } from "./scada";
import { SUPPLY_ASSET_ICON_PATHS } from "./supply-icons";
export const WORKBENCH_LAYER_GROUPS: Record<string, string[]> = {
@@ -33,18 +29,17 @@ export const WORKBENCH_LAYER_GROUPS: Record<string, string[]> = {
export const INITIAL_LAYER_VISIBILITY: Record<string, boolean> = {
...Object.fromEntries(
SUPPLY_LAYER_CATALOG
.filter((layer) => layer.id !== "scada")
.map((layer) => [layer.id, layer.initialVisible])
SUPPLY_LAYER_CATALOG.filter((layer) => layer.id !== "scada").map((layer) => [
layer.id,
layer.initialVisible
])
),
...Object.fromEntries(SCADA_LAYER_CONTROL_ITEMS.map((item) => [item.id, true])),
simulation: false
};
const SOURCE_CONTROL_LABELS = Object.fromEntries(
SUPPLY_LAYER_CATALOG
.filter((layer) => layer.available)
.map((layer) => [layer.id, layer.label])
SUPPLY_LAYER_CATALOG.filter((layer) => layer.available).map((layer) => [layer.id, layer.label])
) as Record<WaterNetworkSourceId, string>;
type LayerControlPresentation = {
@@ -55,7 +50,10 @@ type LayerControlPresentation = {
visual: MapLayerControlVisual;
};
const LAYER_CONTROL_PRESENTATION: Record<Exclude<WaterNetworkSourceId, "scada">, LayerControlPresentation> = {
const LAYER_CONTROL_PRESENTATION: Record<
Exclude<WaterNetworkSourceId, "scada">,
LayerControlPresentation
> = {
pipes: {
description: "按管径分级",
minZoom: 8.5,
@@ -258,14 +256,70 @@ export const MAP_LEGEND_ITEMS: MapLegendItem[] = [
layerControlId: "pipes",
...MODEL_PIPE_LEGEND_GROUP
},
{ id: "junction", label: "节点", color: MAP_STYLE_TOKENS.supply.junction, shape: "dot", layerControlId: "junctions", ...MODEL_FACILITY_LEGEND_GROUP },
{ id: "valve", label: "阀门", color: MAP_STYLE_TOKENS.supply.valve, imageSrc: SUPPLY_ASSET_ICON_PATHS.valve, layerControlId: "valves", ...MODEL_FACILITY_LEGEND_GROUP },
{ id: "reservoir", label: "水库", color: MAP_STYLE_TOKENS.supply.reservoir, imageSrc: SUPPLY_ASSET_ICON_PATHS.reservoir, layerControlId: "reservoirs", ...MODEL_FACILITY_LEGEND_GROUP },
{ id: "pump", label: "水泵", color: MAP_STYLE_TOKENS.supply.pump, imageSrc: SUPPLY_ASSET_ICON_PATHS.pump, layerControlId: "pumps", ...MODEL_FACILITY_LEGEND_GROUP },
{ id: "tank", label: "水箱", color: MAP_STYLE_TOKENS.supply.tank, imageSrc: SUPPLY_ASSET_ICON_PATHS.tank, layerControlId: "tanks", ...MODEL_FACILITY_LEGEND_GROUP },
{ id: "scada-flow", label: "流量监测", color: MAP_STYLE_TOKENS.supply.scadaFlow, imageSrc: SCADA_ICON_PATHS.flow, layerControlId: "scada-flow", ...MONITORING_LEGEND_GROUP },
{ id: "scada-pressure", label: "压力监测", color: MAP_STYLE_TOKENS.supply.scadaPressure, imageSrc: SCADA_ICON_PATHS.pressure, layerControlId: "scada-pressure", ...MONITORING_LEGEND_GROUP },
{ id: "water-quality-monitoring", label: "水质监测", color: MAP_STYLE_TOKENS.supply.waterQuality, imageSrc: SUPPLY_ASSET_ICON_PATHS.waterQuality, layerControlId: "water_quality_monitoring_points", ...MONITORING_LEGEND_GROUP },
{
id: "junction",
label: "节点",
color: MAP_STYLE_TOKENS.supply.junction,
shape: "dot",
layerControlId: "junctions",
...MODEL_FACILITY_LEGEND_GROUP
},
{
id: "valve",
label: "阀门",
color: MAP_STYLE_TOKENS.supply.valve,
imageSrc: SUPPLY_ASSET_ICON_PATHS.valve,
layerControlId: "valves",
...MODEL_FACILITY_LEGEND_GROUP
},
{
id: "reservoir",
label: "水库",
color: MAP_STYLE_TOKENS.supply.reservoir,
imageSrc: SUPPLY_ASSET_ICON_PATHS.reservoir,
layerControlId: "reservoirs",
...MODEL_FACILITY_LEGEND_GROUP
},
{
id: "pump",
label: "水泵",
color: MAP_STYLE_TOKENS.supply.pump,
imageSrc: SUPPLY_ASSET_ICON_PATHS.pump,
layerControlId: "pumps",
...MODEL_FACILITY_LEGEND_GROUP
},
{
id: "tank",
label: "水箱",
color: MAP_STYLE_TOKENS.supply.tank,
imageSrc: SUPPLY_ASSET_ICON_PATHS.tank,
layerControlId: "tanks",
...MODEL_FACILITY_LEGEND_GROUP
},
{
id: "scada-flow",
label: "流量监测",
color: MAP_STYLE_TOKENS.supply.scadaFlow,
imageSrc: SCADA_ICON_PATHS.flow,
layerControlId: "scada-flow",
...MONITORING_LEGEND_GROUP
},
{
id: "scada-pressure",
label: "压力监测",
color: MAP_STYLE_TOKENS.supply.scadaPressure,
imageSrc: SCADA_ICON_PATHS.pressure,
layerControlId: "scada-pressure",
...MONITORING_LEGEND_GROUP
},
{
id: "water-quality-monitoring",
label: "水质监测",
color: MAP_STYLE_TOKENS.supply.waterQuality,
imageSrc: SUPPLY_ASSET_ICON_PATHS.waterQuality,
layerControlId: "water_quality_monitoring_points",
...MONITORING_LEGEND_GROUP
},
{
id: "source-major-pipe",
label: "DN600 及以上",
@@ -311,36 +365,101 @@ export const MAP_LEGEND_ITEMS: MapLegendItem[] = [
layerControlId: "fx_20200811_dn100_pipes",
...SOURCE_AUGUST_PIPE_LEGEND_GROUP
},
{ id: "source-valve", label: "GIS 阀门", color: MAP_STYLE_TOKENS.supply.sourceValve, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceValve, layerControlId: "fx_20200423_valves", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-drain-valve", label: "排水阀", color: MAP_STYLE_TOKENS.supply.sourceDrainValve, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceDrainValve, layerControlId: "fx_20200423_drain_valves", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-flow-meter", label: "流量仪", color: MAP_STYLE_TOKENS.supply.sourceFlowMeter, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceFlowMeter, layerControlId: "fx_20200423_flow_meters", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-hydrant", label: "消火栓", color: MAP_STYLE_TOKENS.supply.sourceHydrant, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceHydrant, layerControlId: "fx_20200423_hydrants", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-connection", label: "接水点", color: MAP_STYLE_TOKENS.supply.sourceConnection, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceConnection, layerControlId: "fx_20200423_service_connections", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-customer-meter", label: "用户水表", color: MAP_STYLE_TOKENS.supply.sourceCustomerMeter, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceCustomerMeter, layerControlId: "fx_20200423_customer_meters", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "source-non-residential-meter", label: "非居民水表", color: MAP_STYLE_TOKENS.supply.sourceNonResidentialMeter, imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceNonResidentialMeter, layerControlId: "fx_20200423_non_residential_meters", ...SOURCE_FACILITY_LEGEND_GROUP },
{ id: "impact-area", label: "影响范围", description: "事故或分析结果", color: MAP_STYLE_TOKENS.state.incident, shape: "area", layerControlId: "simulation", ...ANALYSIS_LEGEND_GROUP }
{
id: "source-valve",
label: "GIS 阀门",
color: MAP_STYLE_TOKENS.supply.sourceValve,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceValve,
layerControlId: "fx_20200423_valves",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-drain-valve",
label: "排水阀",
color: MAP_STYLE_TOKENS.supply.sourceDrainValve,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceDrainValve,
layerControlId: "fx_20200423_drain_valves",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-flow-meter",
label: "流量仪",
color: MAP_STYLE_TOKENS.supply.sourceFlowMeter,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceFlowMeter,
layerControlId: "fx_20200423_flow_meters",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-hydrant",
label: "消火栓",
color: MAP_STYLE_TOKENS.supply.sourceHydrant,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceHydrant,
layerControlId: "fx_20200423_hydrants",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-connection",
label: "接水点",
color: MAP_STYLE_TOKENS.supply.sourceConnection,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceConnection,
layerControlId: "fx_20200423_service_connections",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-customer-meter",
label: "用户水表",
color: MAP_STYLE_TOKENS.supply.sourceCustomerMeter,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceCustomerMeter,
layerControlId: "fx_20200423_customer_meters",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "source-non-residential-meter",
label: "非居民水表",
color: MAP_STYLE_TOKENS.supply.sourceNonResidentialMeter,
imageSrc: SUPPLY_ASSET_ICON_PATHS.sourceNonResidentialMeter,
layerControlId: "fx_20200423_non_residential_meters",
...SOURCE_FACILITY_LEGEND_GROUP
},
{
id: "impact-area",
label: "影响范围",
description: "事故或分析结果",
color: MAP_STYLE_TOKENS.state.incident,
shape: "area",
layerControlId: "simulation",
...ANALYSIS_LEGEND_GROUP
}
];
export function createVisibleMapLegendItems(
layerItems: readonly Pick<MapLayerControlItem, "id" | "visible">[]
) {
const visibleLayerIds = new Set(
layerItems.filter((item) => item.visible).map((item) => item.id)
);
const visibleLayerIds = new Set(layerItems.filter((item) => item.visible).map((item) => item.id));
return MAP_LEGEND_ITEMS.filter((item) =>
item.layerControlId ? visibleLayerIds.has(item.layerControlId) : true
);
}
export const BASE_LAYER_IDS = ["mapbox-light", "mapbox-satellite"] as const;
export const BASE_LAYER_GROUPS = {
"mapbox-light": ["mapbox-light"],
"mapbox-satellite": ["mapbox-satellite"],
"tianditu-vector": ["tianditu-vector", "tianditu-vector-annotation"],
"tianditu-image": ["tianditu-image", "tianditu-image-annotation"]
} as const;
export const BASE_LAYER_IDS = Object.values(BASE_LAYER_GROUPS).flat();
export function applyBaseLayerVisibility(map: MapLibreMap, activeBaseLayerId: string) {
const activeLayerIds = new Set<string>(
BASE_LAYER_GROUPS[activeBaseLayerId as keyof typeof BASE_LAYER_GROUPS] ?? []
);
BASE_LAYER_IDS.forEach((layerId) => {
if (map.getLayer(layerId)) {
map.setLayoutProperty(
layerId,
"visibility",
activeBaseLayerId === layerId ? "visible" : "none"
activeLayerIds.has(layerId) ? "visible" : "none"
);
}
});
@@ -359,6 +478,18 @@ export const BASE_LAYER_OPTIONS: BaseLayerOption[] = [
description: "卫星街道",
swatchClassName: "bg-gradient-to-br from-emerald-950 via-emerald-700 to-yellow-200"
},
{
id: "tianditu-vector",
label: "天地图矢量",
description: "矢量与中文注记",
swatchClassName: "bg-gradient-to-br from-sky-50 via-stone-100 to-emerald-100"
},
{
id: "tianditu-image",
label: "天地图影像",
description: "影像与中文注记",
swatchClassName: "bg-gradient-to-br from-slate-950 via-emerald-800 to-stone-300"
},
{
id: "none",
label: "无底图",
@@ -390,20 +521,23 @@ export function createLayerControlItems(
groupLabel: "管网模型",
groupDescription: "计算模型与基础设施"
})),
...(available.has("scada") ? SCADA_LAYER_CONTROL_ITEMS.map((item) => ({
id: item.id,
label: item.label,
description: item.description,
minZoom: 8.5,
visual: {
kind: "image" as const,
imageSrc: item.id === "scada-pressure" ? SCADA_ICON_PATHS.pressure : SCADA_ICON_PATHS.flow
},
visible: layerVisibility[item.id],
group: "monitoring",
groupLabel: "监测点",
groupDescription: "在线监测与分析选点"
})) : []),
...(available.has("scada")
? SCADA_LAYER_CONTROL_ITEMS.map((item) => ({
id: item.id,
label: item.label,
description: item.description,
minZoom: 8.5,
visual: {
kind: "image" as const,
imageSrc:
item.id === "scada-pressure" ? SCADA_ICON_PATHS.pressure : SCADA_ICON_PATHS.flow
},
visible: layerVisibility[item.id],
group: "monitoring",
groupLabel: "监测点",
groupDescription: "在线监测与分析选点"
}))
: []),
...monitoringItems.map((layer) => ({
id: layer.id,
label: SOURCE_CONTROL_LABELS[layer.id],
@@ -443,8 +577,9 @@ export function getWorkbenchLayerIds(
groupId: string
) {
if (groupId in SCADA_LAYER_GROUPS) {
return SCADA_LAYER_GROUPS[groupId as keyof typeof SCADA_LAYER_GROUPS]
.filter((layerId) => map.getLayer(layerId));
return SCADA_LAYER_GROUPS[groupId as keyof typeof SCADA_LAYER_GROUPS].filter((layerId) =>
map.getLayer(layerId)
);
}
if (isWaterNetworkSourceId(groupId)) {
return map