feat: initialize TJWater WebGIS frontend

This commit is contained in:
2026-07-21 18:31:08 +08:00
commit ad5e5d3133
191 changed files with 37963 additions and 0 deletions
@@ -0,0 +1,546 @@
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<string, string> = {
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: "井底高程",
v_type: "阀门类型",
setting: "阀门设定",
minor_loss: "局部损失系数",
orifice_type: "孔口类型",
discharge_coeff: "流量系数",
gated: "闸门",
offset_height: "偏移高度",
geom1: "几何参数 1",
geom2: "几何参数 2",
pump_curve: "泵曲线",
startup_depth: "启动水深",
shutoff_depth: "停泵水深",
outfall_type: "排放类型",
stage_data: "阶段数据",
status: "状态",
head: "水头",
pattern: "模式",
init_level: "初始水位",
min_level: "最低水位",
max_level: "最高水位",
material: "材质",
elevation: "高程",
demand: "需水量",
pressure: "压力",
flow: "流量",
velocity: "流速",
zone: "分区",
district: "片区",
source: "来源",
updated_at: "更新时间",
point_id: "测点 ID",
point_external_id: "测点编号",
point_name: "测点名称",
associated_element_id: "关联资产",
transmission_mode: "传输方式",
transmission_frequency: "传输频率",
reliability: "可靠性",
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<string, number> = {
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<string, string> = {
on: "运行",
off: "停止",
active: "运行中",
inactive: "停用",
online: "在线",
offline: "离线",
open: "开启",
closed: "关闭",
normal: "正常",
warning: "告警",
critical: "严重"
};
const VALVE_TYPE_LABELS: Record<string, string> = {
prv: "减压阀",
psv: "持压阀",
pbv: "压降控制阀",
fcv: "流量控制阀",
tcv: "节流控制阀",
gpv: "通用阀",
cv: "止回阀"
};
const MATERIAL_LABELS: Record<string, string> = {
di: "球墨铸铁",
ci: "铸铁",
steel: "钢管",
st: "钢管",
pvc: "PVC",
pe: "PE",
hdpe: "HDPE",
concrete: "混凝土",
rc: "钢筋混凝土"
};
const ASSET_TYPE_LABELS: Record<string, string> = {
pipe: "管线",
conduit: "管线",
junction: "节点",
node: "节点",
valve: "阀门",
reservoir: "水库",
tank: "水箱",
pump: "泵站",
scada: "SCADA",
pressure: "压力监测",
pipe_flow: "流量监测"
};
const TRANSMISSION_MODE_LABELS: Record<string, string> = {
wired: "有线",
wire: "有线",
cable: "有线",
wireless: "无线",
cellular: "蜂窝网络",
mobile: "移动网络",
gprs: "GPRS",
"4g": "4G",
"5g": "5G",
nb_iot: "NB-IoT",
nbiot: "NB-IoT",
lora: "LoRa",
realtime: "实时",
real_time: "实时",
non_realtime: "非实时",
non_real_time: "非实时"
};
const RELIABILITY_LABELS: Record<string, string> = {
high: "高",
medium: "中",
mid: "中",
low: "低",
good: "良好",
fair: "一般",
poor: "较差"
};
const PATTERN_LABELS: Record<string, string> = {
none: "无",
null: "无",
default: "默认"
};
const OUTFALL_TYPE_LABELS: Record<string, string> = {
free: "自由出流"
};
const GATED_LABELS: Record<string, string> = {
no: "否",
false: "否",
"0": "否",
yes: "是",
true: "是",
"1": "是"
};
const SCADA_KIND_LABELS: Record<string, string> = {
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<string, string> = {
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<WaterNetworkSourceId, readonly string[]> = {
pipes: ["id", "diameter", "length", "material"],
junctions: ["id", "elevation", "demand", "pressure"],
valves: ["id", "node1", "node2", "diameter", "v_type", "setting"],
reservoirs: ["id", "head", "pattern"],
scada: ["id", "type", "associated_element_id", "transmission_mode", "transmission_frequency", "reliability"],
pumps: ["id", "node1", "node2", "status"],
tanks: ["id", "elevation", "init_level", "min_level", "max_level"]
};
const FEATURE_PANEL_BADGE_KEYS: Partial<Record<WaterNetworkSourceId, string>> = {
pipes: "status",
valves: "setting"
};
type FeatureInsightMetricDefinition = {
label: string;
key?: string;
value?: string;
};
const FEATURE_INSIGHT_METRICS: Record<WaterNetworkSourceId, readonly FeatureInsightMetricDefinition[]> = {
pipes: [
{ label: "管径", key: "diameter" },
{ label: "长度", key: "length" },
{ label: "材质", key: "material" },
{ label: "状态", key: "status" }
],
junctions: [
{ label: "需水量", key: "demand" },
{ label: "高程", key: "elevation" },
{ label: "压力", key: "pressure" },
{ label: "状态", value: "在线" }
],
valves: [
{ label: "管径", key: "diameter" },
{ label: "类型", key: "v_type" },
{ label: "设定", key: "setting" },
{ label: "局部损失", key: "minor_loss" }
],
reservoirs: [
{ label: "水头", key: "head" },
{ label: "模式", key: "pattern" },
{ label: "状态", value: "在线" }
],
scada: [
{ label: "类型", key: "type" },
{ label: "关联资产", key: "associated_element_id" },
{ label: "传输方式", key: "transmission_mode" },
{ label: "可靠性", key: "reliability" }
],
pumps: [
{ label: "起点", key: "node1" },
{ label: "终点", key: "node2" },
{ label: "状态", key: "status" }
],
tanks: [
{ label: "高程", key: "elevation" },
{ label: "初始水位", key: "init_level" },
{ label: "最高水位", key: "max_level" },
{ label: "状态", value: "在线" }
]
};
export function getLocalizedFeatureProperties(properties: Record<string, unknown>): 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<string, unknown>,
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<string, unknown>
): 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<string, unknown>
): FeatureInsightMetric[] {
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<string, unknown>,
featureId?: string
): FeaturePanelModel {
const entries = getFeaturePanelProperties(layer, properties, featureId);
const idKey = "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 (typeof value === "string") {
const mappedValue = getMappedPropertyValue(normalizedKey, value);
if (mappedValue) return mappedValue;
}
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 diameterInMillimeters = typeof value === "number" ? value : Number(value);
return Number.isFinite(diameterInMillimeters)
? `${formatValue(diameterInMillimeters)} 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<string, unknown>, 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 getMappedPropertyValue(normalizedKey: string, value: string) {
const normalizedValue = normalizePropertyValue(value);
if (isStatusLikeKey(normalizedKey)) {
return STATUS_LABELS[normalizedValue];
}
if (normalizedKey === "v_type") {
return VALVE_TYPE_LABELS[normalizedValue];
}
if (normalizedKey === "material") {
return MATERIAL_LABELS[normalizedValue];
}
if (normalizedKey === "type") {
return ASSET_TYPE_LABELS[normalizedValue];
}
if (normalizedKey === "transmission_mode") {
return TRANSMISSION_MODE_LABELS[normalizedValue];
}
if (normalizedKey === "reliability") {
return RELIABILITY_LABELS[normalizedValue];
}
if (normalizedKey === "pattern") {
return PATTERN_LABELS[normalizedValue];
}
return undefined;
}
function normalizePropertyValue(value: string) {
return value.trim().replaceAll("-", "_").replaceAll(" ", "_").toLowerCase();
}
function isStatusLikeKey(normalizedKey: string) {
return normalizedKey === "status" || normalizedKey === "setting";
}
function getFeaturePanelBadge(
layer: WaterNetworkSourceId,
value: string
): FeaturePanelBadge {
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" };
}