import type { WaterNetworkSourceId } from "../map/sources"; import { formatValue } from "./format-value"; export type LocalizedFeatureProperty = { key: string; label: string; value: string; }; export type FeaturePanelBadgeTone = "active" | "inactive" | "warning" | "critical" | "info"; export type FeaturePanelBadge = { label: string; tone: FeaturePanelBadgeTone; }; export type FeaturePanelModel = { id: string; badge?: FeaturePanelBadge; attributes: LocalizedFeatureProperty[]; }; export type FeatureInsightMetric = { label: string; value: string; }; const PROPERTY_LABELS: Record = { fid: "GeoServer 要素 ID", id: "编号", scada_id: "编号 UUID", sensor_id: "传感器 ID", sensor_name: "传感器名称", swmm_node: "SWMM 节点", topology_order: "拓扑序", distance_to_wwtp_m: "距污水厂距离", upstream_node_count: "上游节点数", upstream_sensors: "上游传感器", downstream_path: "下游路径", x: "X 坐标", y: "Y 坐标", cod_mg_l: "COD", 氨氮_mg_l: "氨氮", 电导率_us_cm: "电导率", 液位_m: "液位", 流量_m3_s: "流量", gid: "要素编号", name: "名称", code: "编码", type: "类型", node1: "起点节点", node2: "终点节点", from_node: "起点节点", to_node: "终点节点", diameter: "管径", dn: "管径", length: "长度", roughness: "粗糙系数", shape: "断面形状", max_depth: "最大深度", invert_elevation: "井底高程", orifice_type: "孔口类型", discharge_coeff: "流量系数", gated: "闸门", offset_height: "偏移高度", geom1: "几何参数 1", geom2: "几何参数 2", pump_curve: "泵曲线", startup_depth: "启动水深", shutoff_depth: "停泵水深", outfall_type: "排放类型", stage_data: "阶段数据", status: "状态", material: "材质", elevation: "高程", demand: "需水量", pressure: "压力", flow: "流量", velocity: "流速", zone: "分区", district: "片区", source: "来源", updated_at: "更新时间", point_id: "测点 ID", point_external_id: "测点编号", point_name: "测点名称", device_id: "设备 ID", device_external_id: "设备编号", device_type_id: "设备类型 ID", device_type_name: "设备类型", device_icon_code: "设备图标编码", active: "运行状态", external_id: "设备编号", junction_id: "关联检查井", kind: "设备类型", location_source: "位置来源", site_external_id: "站点编号", synced_at: "同步时间" }; const PROPERTY_ORDER: Record = { fid: 9, id: 10, scada_id: 10, sensor_id: 10, sensor_name: 12, swmm_node: 13, topology_order: 14, distance_to_wwtp_m: 15, upstream_node_count: 16, upstream_sensors: 17, downstream_path: 18, cod_mg_l: 20, 氨氮_mg_l: 21, 电导率_us_cm: 22, 液位_m: 23, 流量_m3_s: 24, gid: 11, name: 12, code: 13, diameter: 20, dn: 20, length: 21, roughness: 22, max_depth: 30, invert_elevation: 31, orifice_type: 32, outfall_type: 32, shape: 33, pump_curve: 34, material: 23, node1: 24, from_node: 24, node2: 25, to_node: 25, elevation: 30, demand: 31, pressure: 32, flow: 33, velocity: 34, status: 90 }; const STATUS_LABELS: Record = { on: "运行", off: "停止", active: "运行中", inactive: "停用", online: "在线", offline: "离线", open: "开启", closed: "关闭", normal: "正常", warning: "告警", critical: "严重" }; const OUTFALL_TYPE_LABELS: Record = { free: "自由出流" }; const GATED_LABELS: Record = { no: "否", false: "否", "0": "否", yes: "是", true: "是", "1": "是" }; const SCADA_KIND_LABELS: Record = { water_quality: "水质监测", radar_level: "雷达液位", ultrasonic_flow: "超声流量", integrated_monitoring: "综合监测点" }; export const SCADA_METRIC_PROPERTY_KEYS = [ "COD_mg_L", "氨氮_mg_L", "电导率_uS_cm", "液位_m", "流量_m3_s" ] as const; const PROPERTY_UNITS: Record = { cod_mg_l: "mg/L", 氨氮_mg_l: "mg/L", 电导率_us_cm: "μS/cm", 液位_m: "m", 流量_m3_s: "m³/s" }; const FEATURE_PANEL_PROPERTY_KEYS: Record = { pumps: ["id", "status", "startup_depth", "shutoff_depth"], conduits: ["id", "length", "diameter"], junctions: ["id", "invert_elevation", "max_depth"], orifices: ["id", "discharge_coeff", "gated", "shape", "geom1", "geom2"], outfalls: ["id", "invert_elevation", "outfall_type"], scada: [ "sensor_id", "kind", "swmm_node", "topology_order", "upstream_node_count", "distance_to_wwtp_m" ] }; const FEATURE_PANEL_BADGE_KEYS: Partial> = { pumps: "status", orifices: "gated", scada: undefined }; type FeatureInsightMetricDefinition = { label: string; key?: string; value?: string; }; const FEATURE_INSIGHT_METRICS: Record< Exclude, readonly FeatureInsightMetricDefinition[] > = { conduits: [ { label: "管径", key: "diameter" }, { label: "长度", key: "length" }, { label: "粗糙系数", key: "roughness" }, { label: "状态", key: "status" } ], junctions: [ { label: "最大深度", key: "max_depth" }, { label: "井底高程", key: "invert_elevation" }, { label: "对象类型", value: "检查井" }, { label: "状态", value: "在线" } ], orifices: [ { label: "孔口类型", key: "orifice_type" }, { label: "断面形状", key: "shape" }, { label: "流量系数", key: "discharge_coeff" }, { label: "闸门", key: "gated" } ], pumps: [ { label: "状态", key: "status" }, { label: "泵曲线", key: "pump_curve" }, { label: "启动水深", key: "startup_depth" }, { label: "停泵水深", key: "shutoff_depth" } ], outfalls: [ { label: "排放类型", key: "outfall_type" }, { label: "井底高程", key: "invert_elevation" }, { label: "阶段数据", key: "stage_data" }, { label: "状态", value: "在线" } ] }; export function getLocalizedFeatureProperties(properties: Record): LocalizedFeatureProperty[] { return Object.entries(properties).filter(([key]) => normalizePropertyKey(key) !== "cluster_size").map(([key, value]) => ({ key, label: getFeaturePropertyLabel(key), value: formatFeaturePropertyValue(key, value) })).sort((first, second) => getPropertyOrder(first.key) - getPropertyOrder(second.key)); } export function getFeaturePanelProperties( layer: WaterNetworkSourceId, properties: Record, featureId?: string ): LocalizedFeatureProperty[] { return FEATURE_PANEL_PROPERTY_KEYS[layer].map((key) => { const propertyValue = getPropertyValue(properties, key); const value = key === "id" || key === "scada_id" || key === "sensor_id" ? propertyValue ?? featureId : propertyValue; return { key, label: getFeaturePropertyLabel(key), value: formatFeaturePropertyValue(key, value) }; }); } export function getScadaMetricProperties( properties: Record ): LocalizedFeatureProperty[] { return SCADA_METRIC_PROPERTY_KEYS.map((key) => ({ key, label: getFeaturePropertyLabel(key), value: formatFeaturePropertyValue(key, getPropertyValue(properties, key)) })); } export function getFeatureInsightMetrics( layer: WaterNetworkSourceId, properties: Record ): FeatureInsightMetric[] { if (layer === "scada") { return getScadaMetricProperties(properties).map(({ label, value }) => ({ label, value })); } return FEATURE_INSIGHT_METRICS[layer].map((metric) => ({ label: metric.label, value: metric.key ? formatFeaturePropertyValue(metric.key, getPropertyValue(properties, metric.key)) : metric.value ?? "暂无" })); } export function getFeaturePanelModel( layer: WaterNetworkSourceId, properties: Record, featureId?: string ): FeaturePanelModel { const entries = getFeaturePanelProperties(layer, properties, featureId); const idKey = layer === "scada" ? "sensor_id" : "id"; const id = entries.find((entry) => entry.key === idKey)?.value ?? "暂无"; const badgeKey = FEATURE_PANEL_BADGE_KEYS[layer]; const badgeEntry = badgeKey ? entries.find((entry) => entry.key === badgeKey) : undefined; return { id, badge: badgeEntry ? getFeaturePanelBadge(layer, badgeEntry.value) : undefined, attributes: entries.filter((entry) => entry.key !== idKey && entry.key !== badgeKey) }; } export function getFeaturePropertyLabel(key: string) { const normalizedKey = normalizePropertyKey(key); return PROPERTY_LABELS[normalizedKey] ?? key; } export function formatFeaturePropertyValue(key: string, value: unknown) { const normalizedKey = normalizePropertyKey(key); if (isStatusKey(key) && typeof value === "string") { return STATUS_LABELS[value.toLowerCase()] ?? value; } if (normalizedKey === "outfall_type" && typeof value === "string") { return OUTFALL_TYPE_LABELS[value.toLowerCase()] ?? value; } if (normalizedKey === "gated" && value !== null && value !== undefined && value !== "") { return GATED_LABELS[String(value).toLowerCase()] ?? formatValue(value); } if (normalizedKey === "active" && typeof value === "boolean") { return value ? "在线" : "离线"; } if (normalizedKey === "location_source" && value === "density_cluster_demo") { return "密度聚类模拟位置"; } if (normalizedKey === "location_source" && value === "uniform_farthest_point_demo") { return "空间均匀采样模拟位置"; } if (normalizedKey === "kind" && typeof value === "string") { return SCADA_KIND_LABELS[value] ?? value; } const unit = PROPERTY_UNITS[normalizedKey]; if (unit && value !== null && value !== undefined && value !== "") { return `${formatValue(value)} ${unit}`; } if ((normalizedKey === "diameter" || normalizedKey === "dn") && value !== null && value !== undefined && value !== "") { const diameterInMeters = typeof value === "number" ? value : Number(value); return Number.isFinite(diameterInMeters) ? `${formatValue(diameterInMeters * 1000)} mm` : formatValue(value); } if (["length", "elevation", "invert_elevation", "max_depth", "startup_depth", "shutoff_depth", "distance_to_wwtp_m"].includes(normalizedKey) && value !== null && value !== undefined && value !== "") { return `${formatValue(value)} m`; } return formatValue(value); } function getPropertyOrder(key: string) { return PROPERTY_ORDER[normalizePropertyKey(key)] ?? 100; } function normalizePropertyKey(key: string) { return key.trim().replaceAll("-", "_").toLowerCase(); } function getPropertyValue(properties: Record, key: string) { if (Object.prototype.hasOwnProperty.call(properties, key)) { return properties[key]; } const normalizedKey = normalizePropertyKey(key); const matchingKey = Object.keys(properties).find((propertyKey) => normalizePropertyKey(propertyKey) === normalizedKey); return matchingKey ? properties[matchingKey] : undefined; } function isStatusKey(key: string) { return normalizePropertyKey(key) === "status"; } function getFeaturePanelBadge( layer: WaterNetworkSourceId, value: string ): FeaturePanelBadge { if (layer === "orifices") { return value === "是" ? { label: "有闸门", tone: "info" } : value === "否" ? { label: "无闸门", tone: "inactive" } : { label: `闸门 ${value}`, tone: "inactive" }; } const normalizedValue = value.toLowerCase(); if (["运行", "运行中", "在线", "开启", "正常"].includes(value)) { return { label: value, tone: "active" }; } if (["停止", "停用", "离线", "关闭"].includes(value)) { return { label: value, tone: "inactive" }; } if (normalizedValue === "warning" || value === "告警") { return { label: value, tone: "warning" }; } if (normalizedValue === "critical" || value === "严重") { return { label: value, tone: "critical" }; } return { label: value, tone: "inactive" }; }