From 265ecdc795d5fde4cde169f14c293863505772ae Mon Sep 17 00:00:00 2001 From: JIANG Date: Thu, 30 Oct 2025 11:57:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A4=9A=E4=B8=AA=E5=9B=BE?= =?UTF-8?q?=E5=B1=82=EF=BC=9B=E6=9B=B4=E6=96=B0=E5=9B=BE=E5=B1=82=E5=9B=BE?= =?UTF-8?q?=E6=A0=87=EF=BC=9B=E4=BF=AE=E5=A4=8DSCADA=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=8A=B6=E6=80=81=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/icons/pump.svg | 2 +- public/icons/reservior.svg | 1 + public/icons/reservoirs.svg | 1 - public/icons/scada_flow.svg | 2 +- public/icons/scada_pressure.svg | 2 +- public/icons/tank.svg | 2 +- public/icons/valve.svg | 1 + src/app/OlMap/MapComponent.tsx | 99 ++++++++++++++++++++++-- src/components/olmap/SCADADeviceList.tsx | 52 ++++++------- 9 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 public/icons/reservior.svg delete mode 100644 public/icons/reservoirs.svg create mode 100644 public/icons/valve.svg diff --git a/public/icons/pump.svg b/public/icons/pump.svg index 66319cf..98b55c9 100644 --- a/public/icons/pump.svg +++ b/public/icons/pump.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/reservior.svg b/public/icons/reservior.svg new file mode 100644 index 0000000..3962a95 --- /dev/null +++ b/public/icons/reservior.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/reservoirs.svg b/public/icons/reservoirs.svg deleted file mode 100644 index d5ed17f..0000000 --- a/public/icons/reservoirs.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/icons/scada_flow.svg b/public/icons/scada_flow.svg index 75baf92..ea7cda3 100644 --- a/public/icons/scada_flow.svg +++ b/public/icons/scada_flow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/scada_pressure.svg b/public/icons/scada_pressure.svg index 3adb45a..0ac4d80 100644 --- a/public/icons/scada_pressure.svg +++ b/public/icons/scada_pressure.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/tank.svg b/public/icons/tank.svg index b9562af..751c98a 100644 --- a/public/icons/tank.svg +++ b/public/icons/tank.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/icons/valve.svg b/public/icons/valve.svg new file mode 100644 index 0000000..84c3055 --- /dev/null +++ b/public/icons/valve.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index 539c84b..2e294fb 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -18,7 +18,7 @@ import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile"; import MVT from "ol/format/MVT"; import { FlatStyleLike } from "ol/style/flat"; import { toLonLat } from "ol/proj"; -import { along, bearing, lineString, length } from "@turf/turf"; +import { along, bearing, lineString, length, toMercator } from "@turf/turf"; import { Deck } from "@deck.gl/core"; import { TextLayer } from "@deck.gl/layers"; import { TripsLayer } from "@deck.gl/geo-layers"; @@ -26,7 +26,9 @@ import { CollisionFilterExtension } from "@deck.gl/extensions"; import VectorSource from "ol/source/Vector"; import GeoJson from "ol/format/GeoJSON"; import VectorLayer from "ol/layer/Vector"; -import { Style, Icon } from "ol/style"; +import { Icon, Style } from "ol/style.js"; +import { FeatureLike } from "ol/Feature"; +import { Point } from "ol/geom"; interface MapComponentProps { children?: React.ReactNode; @@ -189,6 +191,52 @@ const MapComponent: React.FC = ({ children }) => { }), }); }; + // 定义 reservoirs 图层的样式函数,使用固定图标 + const reservoirsStyle = () => { + const reserviorIcon = "/icons/reservior.svg"; + return new Style({ + image: new Icon({ + src: reserviorIcon, + scale: 0.1, // 根据需要调整图标大小 + anchor: [0.5, 0.5], // 图标锚点居中 + }), + }); + }; + // 定义 valves 图层的样式函数,使用固定图标 + const valvesStyle = function (feature: FeatureLike) { + const styles = []; + const valveIcon = "/icons/valve.svg"; + + const geometry = feature.getGeometry(); + const lineCoords = + geometry?.getType() === "LineString" + ? (geometry as any).getCoordinates() + : null; + if (geometry) { + const lineCoordsWGS84 = lineCoords.map((coord: []) => { + const [lon, lat] = toLonLat(coord); + return [lon, lat]; + }); + // 计算中点 + const lineStringFeature = lineString(lineCoordsWGS84); + const lineLength = length(lineStringFeature); + const midPoint = along(lineStringFeature, lineLength / 2).geometry + .coordinates; + // 在中点添加 icon 样式 + const midPointMercator = toMercator(midPoint); + styles.push( + new Style({ + geometry: new Point(midPointMercator), + image: new Icon({ + src: valveIcon, + scale: 0.12, + anchor: [0.5, 0.5], + }), + }) + ); + } + return styles; + }; // 矢量瓦片数据源和图层 const junctionSource = new VectorTileSource({ url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_junctions_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL @@ -204,6 +252,14 @@ const MapComponent: React.FC = ({ children }) => { url: `${mapUrl}/TJWater/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=TJWater:geo_scada&outputFormat=application/json`, format: new GeoJson(), }); + const reservoirsSource = new VectorSource({ + url: `${mapUrl}/TJWater/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=TJWater:geo_reservoirs&outputFormat=application/json`, + format: new GeoJson(), + }); + const valvesSource = new VectorSource({ + url: `${mapUrl}/TJWater/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=TJWater:geo_valves&outputFormat=application/json`, + format: new GeoJson(), + }); // WebGL 渲染优化显示 const junctionLayer = new WebGLVectorTileLayer({ source: junctionSource as any, // 使用 WebGL 渲染 @@ -212,7 +268,7 @@ const MapComponent: React.FC = ({ children }) => { maxZoom: 24, minZoom: 12, properties: { - name: "节点图层", // 设置图层名称 + name: "节点", // 设置图层名称 value: "junctions", type: "point", properties: [ @@ -232,7 +288,7 @@ const MapComponent: React.FC = ({ children }) => { maxZoom: 24, minZoom: 12, properties: { - name: "管道图层", // 设置图层名称 + name: "管道", // 设置图层名称 value: "pipes", type: "linestring", properties: [ @@ -263,6 +319,32 @@ const MapComponent: React.FC = ({ children }) => { properties: [], }, }); + const reservoirsLayer = new VectorLayer({ + source: reservoirsSource, + style: reservoirsStyle, + extent: extent, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "水库", // 设置图层名称 + value: "reservoirs", + type: "point", + properties: [], + }, + }); + const valvesLayer = new VectorLayer({ + source: valvesSource, + style: valvesStyle, + extent: extent, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "阀门", // 设置图层名称 + value: "valves", + type: "linestring", + properties: [], + }, + }); useEffect(() => { if (!mapRef.current) return; // 缓存 junction、pipe 数据,提供给 deck.gl 显示标签使用 @@ -396,10 +478,17 @@ const MapComponent: React.FC = ({ children }) => { const map = new OlMap({ target: mapRef.current, view: new View({ + maxZoom: 24, projection: "EPSG:3857", }), // 图层依面、线、点、标注次序添加 - layers: [pipeLayer, junctionLayer, scadaLayer], + layers: [ + pipeLayer, + junctionLayer, + valvesLayer, + scadaLayer, + reservoirsLayer, + ], controls: [], }); setMap(map); diff --git a/src/components/olmap/SCADADeviceList.tsx b/src/components/olmap/SCADADeviceList.tsx index 4e7a533..e099b5a 100644 --- a/src/components/olmap/SCADADeviceList.tsx +++ b/src/components/olmap/SCADADeviceList.tsx @@ -44,12 +44,25 @@ import { useMap } from "@app/OlMap/MapComponent"; import { GeoJSON } from "ol/format"; import { Point } from "ol/geom"; import config from "@/config/config"; + +const STATUS_OPTIONS: { + value: "online" | "offline" | "warning" | "error"; + name: "在线" | "离线" | "警告" | "错误"; +}[] = [ + { value: "online", name: "在线" }, + { value: "offline", name: "离线" }, + { value: "warning", name: "警告" }, + { value: "error", name: "错误" }, +]; interface SCADADevice { id: string; name: string; type: string; coordinates: [number, number]; - status: "在线" | "离线" | "警告" | "错误"; + status: { + value: "online" | "offline" | "warning" | "error"; + name: "在线" | "离线" | "警告" | "错误"; + }; properties?: Record; } @@ -127,9 +140,7 @@ const SCADADeviceList: React.FC = ({ id: feature.get("id") || feature.getId(), name: feature.get("id") || feature.getId(), type: feature.get("type") === "pipe_flow" ? "流量" : "压力", - status: ["在线", "离线", "警告", "错误"][ - Math.floor(Math.random() * 4) - ] as "在线" | "离线" | "警告" | "错误", + status: STATUS_OPTIONS[Math.floor(Math.random() * 4)], coordinates: (feature.getGeometry() as Point)?.getCoordinates() as [ number, number @@ -137,7 +148,6 @@ const SCADADeviceList: React.FC = ({ properties: feature.getProperties(), })); setInternalDevices(data); - console.log("Fetched SCADA devices:", data); } catch (error) { console.error("Error fetching SCADA devices:", error); } finally { @@ -158,12 +168,7 @@ const SCADADeviceList: React.FC = ({ }, [effectiveDevices]); // 获取设备状态列表 - const deviceStatuses = useMemo(() => { - const statuses = Array.from( - new Set(effectiveDevices.map((device) => device.status)) - ); - return statuses.sort(); - }, [effectiveDevices]); + const deviceStatuses = STATUS_OPTIONS; // 创建设备索引 Map,使用设备 ID 作为键 const deviceIndex = useMemo(() => { @@ -176,29 +181,20 @@ const SCADADeviceList: React.FC = ({ // 过滤设备列表 const filteredDevices = useMemo(() => { - if ( - searchQuery === "" && - selectedType === "all" && - selectedStatus === "all" - ) { - return effectiveDevices; - } - - const searchLower = searchQuery.toLowerCase(); return effectiveDevices.filter((device) => { - if (searchQuery === "") return true; - + const searchLower = searchQuery.toLowerCase(); const nameLower = device.name.toLowerCase(); const idLower = device.id.toLowerCase(); const matchesSearch = + searchQuery === "" || nameLower.indexOf(searchLower) !== -1 || idLower.indexOf(searchLower) !== -1; const matchesType = selectedType === "all" || device.type === selectedType; const matchesStatus = - selectedStatus === "all" || device.status === selectedStatus; + selectedStatus === "all" || device.status.value === selectedStatus; return matchesSearch && matchesType && matchesStatus; }); @@ -396,8 +392,8 @@ const SCADADeviceList: React.FC = ({ > 全部状态 {deviceStatuses.map((status) => ( - - {status} + + {status.name} ))} @@ -486,12 +482,14 @@ const SCADADeviceList: React.FC = ({ - {getStatusIcon(device.status)} + {getStatusIcon(device.status.value)}