diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index 97d6128..78a3314 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -29,6 +29,7 @@ import VectorLayer from "ol/layer/Vector"; import { Icon, Style } from "ol/style.js"; import { FeatureLike } from "ol/Feature"; import { Point } from "ol/geom"; +import VectorTileLayer from "ol/layer/VectorTile"; interface MapComponentProps { children?: React.ReactNode; @@ -193,7 +194,7 @@ const MapComponent: React.FC = ({ children }) => { }); }; // 定义 reservoirs 图层的样式函数,使用固定图标 - const reservoirsStyle = () => { + const reservoirStyle = () => { const reserviorIcon = "/icons/reservior.svg"; return new Style({ image: new Icon({ @@ -203,8 +204,19 @@ const MapComponent: React.FC = ({ children }) => { }), }); }; + // 定义 tanks 图层的样式函数,使用固定图标 + const tankStyle = () => { + const tankIcon = "/icons/tank.svg"; + return new Style({ + image: new Icon({ + src: tankIcon, + scale: 0.1, // 根据需要调整图标大小 + anchor: [0.5, 0.5], // 图标锚点居中 + }), + }); + }; // 定义 valves 图层的样式函数,使用固定图标 - const valvesStyle = function (feature: FeatureLike) { + const valveStyle = function (feature: FeatureLike) { const styles = []; const valveIcon = "/icons/valve.svg"; @@ -238,31 +250,76 @@ const MapComponent: React.FC = ({ children }) => { } return styles; }; + // 定义 pumps 图层的样式函数,使用固定图标 + const pumpStyle = function (feature: FeatureLike) { + const styles = []; + const pumpIcon = "/icons/pump.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: pumpIcon, + scale: 0.12, + anchor: [0.5, 0.5], + }), + }) + ); + } + return styles; + }; // 矢量瓦片数据源和图层 const junctionSource = new VectorTileSource({ - url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_junctions_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL + url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_junctions@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL format: new MVT(), projection: "EPSG:3857", }); const pipeSource = new VectorTileSource({ - url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_pipes_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL + url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_pipes@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL format: new MVT(), projection: "EPSG:3857", }); + const valveSource = new VectorTileSource({ + url: `${MAP_URL}/gwc/service/tms/1.0.0/${MAP_WORKSPACE}:geo_valves@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL + format: new MVT(), + projection: "EPSG:3857", + }); + const reservoirSource = new VectorSource({ + url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_reservoirs&outputFormat=application/json`, + format: new GeoJson(), + }); + const pumpSource = new VectorSource({ + url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_pumps&outputFormat=application/json`, + format: new GeoJson(), + }); + const tankSource = new VectorSource({ + url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_tanks&outputFormat=application/json`, + format: new GeoJson(), + }); const scadaSource = new VectorSource({ url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_scada&outputFormat=application/json`, format: new GeoJson(), }); - const reservoirsSource = new VectorSource({ - url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_reservoirs&outputFormat=application/json`, - format: new GeoJson(), - }); - const valvesSource = new VectorSource({ - url: `${MAP_URL}/${MAP_WORKSPACE}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${MAP_WORKSPACE}:geo_valves&outputFormat=application/json`, - format: new GeoJson(), - }); + // WebGL 渲染优化显示 - const junctionLayer = new WebGLVectorTileLayer({ + const junctionsLayer = new WebGLVectorTileLayer({ source: junctionSource as any, // 使用 WebGL 渲染 style: defaultFlatStyle, extent: MAP_EXTENT, // 设置图层范围 @@ -282,7 +339,7 @@ const MapComponent: React.FC = ({ children }) => { ], }, }); - const pipeLayer = new WebGLVectorTileLayer({ + const pipesLayer = new WebGLVectorTileLayer({ source: pipeSource as any, // 使用 WebGL 渲染 style: defaultFlatStyle, extent: MAP_EXTENT, // 设置图层范围 @@ -307,6 +364,58 @@ const MapComponent: React.FC = ({ children }) => { ], }, }); + const valvesLayer = new VectorTileLayer({ + source: valveSource, + style: valveStyle, + extent: MAP_EXTENT, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "阀门", // 设置图层名称 + value: "valves", + type: "linestring", + properties: [], + }, + }); + const reservoirsLayer = new VectorLayer({ + source: reservoirSource, + style: reservoirStyle, + extent: MAP_EXTENT, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "水库", // 设置图层名称 + value: "reservoirs", + type: "point", + properties: [], + }, + }); + const pumpsLayer = new VectorLayer({ + source: pumpSource, + style: pumpStyle, + extent: MAP_EXTENT, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "水泵", // 设置图层名称 + value: "pumps", + type: "linestring", + properties: [], + }, + }); + const tanksLayer = new VectorLayer({ + source: tankSource, + style: tankStyle, + extent: MAP_EXTENT, // 设置图层范围 + maxZoom: 24, + minZoom: 12, + properties: { + name: "水箱", // 设置图层名称 + value: "tanks", + type: "point", + properties: [], + }, + }); const scadaLayer = new VectorLayer({ source: scadaSource, style: scadaStyle, @@ -320,32 +429,7 @@ const MapComponent: React.FC = ({ children }) => { properties: [], }, }); - const reservoirsLayer = new VectorLayer({ - source: reservoirsSource, - style: reservoirsStyle, - extent: MAP_EXTENT, // 设置图层范围 - maxZoom: 24, - minZoom: 12, - properties: { - name: "水库", // 设置图层名称 - value: "reservoirs", - type: "point", - properties: [], - }, - }); - const valvesLayer = new VectorLayer({ - source: valvesSource, - style: valvesStyle, - extent: MAP_EXTENT, // 设置图层范围 - maxZoom: 24, - minZoom: 12, - properties: { - name: "阀门", // 设置图层名称 - value: "valves", - type: "linestring", - properties: [], - }, - }); + useEffect(() => { if (!mapRef.current) return; // 缓存 junction、pipe 数据,提供给 deck.gl 显示标签使用 @@ -462,20 +546,72 @@ const MapComponent: React.FC = ({ children }) => { console.error("Pipe tile load error:", error); } }); - // 监听 junctionLayer 的 visible 变化 + // 监听 junctionsLayer 的 visible 变化 const handleJunctionVisibilityChange = () => { - const isVisible = junctionLayer.getVisible(); + const isVisible = junctionsLayer.getVisible(); setShowJunctionTextLayer(isVisible); }; - // 监听 pipeLayer 的 visible 变化 + // 监听 pipesLayer 的 visible 变化 const handlePipeVisibilityChange = () => { - const isVisible = pipeLayer.getVisible(); + const isVisible = pipesLayer.getVisible(); setShowPipeTextLayer(isVisible); }; // 添加事件监听器 - junctionLayer.on("change:visible", handleJunctionVisibilityChange); - pipeLayer.on("change:visible", handlePipeVisibilityChange); - + junctionsLayer.on("change:visible", handleJunctionVisibilityChange); + pipesLayer.on("change:visible", handlePipeVisibilityChange); + const availableLayers: any[] = []; + config.MAP_AVAILABLE_LAYERS.forEach((layerValue) => { + switch (layerValue) { + case "junctions": + availableLayers.push(junctionsLayer); + break; + case "pipes": + availableLayers.push(pipesLayer); + break; + case "valves": + availableLayers.push(valvesLayer); + break; + case "reservoirs": + availableLayers.push(reservoirsLayer); + break; + case "pumps": + availableLayers.push(pumpsLayer); + break; + case "tanks": + availableLayers.push(tanksLayer); + break; + case "scada": + availableLayers.push(scadaLayer); + break; + } + }); + // 重新排列图层顺序,确保顺序 点>线>面 + availableLayers.sort((a, b) => { + // 明确顺序(点类优先) + const order = [ + "junctions", + "reservoirs", + "tanks", + "scada", + "pipes", + "valves", + "pumps", + ].reverse(); + // 取值时做安全检查,兼容不同写法(properties.value 或 直接 value) + const getValue = (layer: any) => { + const props = layer.get ? layer.get("properties") : undefined; + return (props && props.value) || layer.get?.("value") || ""; + }; + const aVal = getValue(a); + const bVal = getValue(b); + let ia = order.indexOf(aVal); + let ib = order.indexOf(bVal); + // 如果未在 order 中找到,放到末尾 + if (ia === -1) ia = order.length; + if (ib === -1) ib = order.length; + return ia - ib; + }); + console.log("Available Layers:", availableLayers); const map = new OlMap({ target: mapRef.current, view: new View({ @@ -483,13 +619,7 @@ const MapComponent: React.FC = ({ children }) => { projection: "EPSG:3857", }), // 图层依面、线、点、标注次序添加 - layers: [ - pipeLayer, - junctionLayer, - valvesLayer, - scadaLayer, - reservoirsLayer, - ], + layers: [...availableLayers], controls: [], }); setMap(map); @@ -522,8 +652,8 @@ const MapComponent: React.FC = ({ children }) => { // 清理函数 return () => { - junctionLayer.un("change:visible", handleJunctionVisibilityChange); - pipeLayer.un("change:visible", handlePipeVisibilityChange); + junctionsLayer.un("change:visible", handleJunctionVisibilityChange); + pipesLayer.un("change:visible", handlePipeVisibilityChange); map.setTarget(undefined); map.dispose(); deck.finalize(); diff --git a/src/config/config.ts b/src/config/config.ts index 1ee5ae2..8639686 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -21,7 +21,11 @@ export const config = { 12, // 在缩放级别 24 时,圆形半径为 12px ], }, - // 添加其他配置项... + MAP_AVAILABLE_LAYERS: process.env.NEXT_PUBLIC_MAP_AVAILABLE_LAYERS + ? process.env.NEXT_PUBLIC_MAP_AVAILABLE_LAYERS.split(",").map((item) => + item.trim().toLowerCase() + ) + : ["junctions", "pipes", "valves", "reservoirs", "pumps", "tanks", "scada"], }; export const NETWORK_NAME = process.env.NEXT_PUBLIC_NETWORK_NAME || "tjwater"; export const MAPBOX_TOKEN =