// @refresh reset // 添加此注释强制热重载时重新挂载组件 "use client"; import { config } from "@/config/config"; import React, { createContext, useContext, useState, useEffect, useRef, } from "react"; import { Map as OlMap, VectorTile } from "ol"; import View from "ol/View.js"; import "ol/ol.css"; import MapTools from "./MapTools"; import { Layer } from "ol/layer"; // 保留导入,但用于继承 import VectorTileSource from "ol/source/VectorTile"; import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile"; import MVT from "ol/format/MVT"; import { FlatStyleLike } from "ol/style/flat"; import { toLonLat } from "ol/proj"; import { center } from "@turf/center"; import { bearing } from "@turf/turf"; import { Deck } from "@deck.gl/core"; import { TextLayer } from "@deck.gl/layers"; import { TripsLayer } from "@deck.gl/geo-layers"; // 创建自定义Layer类来包装deck.gl class DeckLayer extends Layer { private deck: Deck; constructor(deckInstance: Deck) { super({}); this.deck = deckInstance; } render(frameState: any): HTMLElement { const { size, viewState } = frameState; const [width, height] = size; const [longitude, latitude] = toLonLat(viewState.center); const zoom = viewState.zoom - 1; // 调整 zoom 以匹配 const bearing = (-viewState.rotation * 180) / Math.PI; const deckViewState = { bearing, longitude, latitude, zoom }; this.deck.setProps({ width, height, viewState: deckViewState }); this.deck.redraw(); // 返回deck.gl的canvas元素 return document.getElementById("deck-canvas") as HTMLElement; } } // 跨组件传递 const MapContext = createContext(undefined); const extent = config.mapExtent; const backendUrl = config.backendUrl; const mapUrl = config.mapUrl; // 添加防抖函数 function debounce any>(func: F, waitFor: number) { let timeout: ReturnType | null = null; return (...args: Parameters): void => { if (timeout !== null) { clearTimeout(timeout); } timeout = setTimeout(() => func(...args), waitFor); }; } export const useMap = () => { return useContext(MapContext); }; const MapComponent: React.FC = () => { const mapRef = useRef(null); const deckRef = useRef(null); const [map, setMap] = useState(); const [currentTime, setCurrentTime] = useState( new Date("2025-09-17T00:30:00+08:00") ); const intervalRef = useRef(null); const [junctionData, setJunctionDataState] = useState([]); const [pipeData, setPipeDataState] = useState([]); const junctionDataIds = useRef(new Set()); const pipeDataIds = useRef(new Set()); const tileJunctionDataBuffer = useRef([]); const tilePipeDataBuffer = useRef([]); let showJunctionText = true; // 控制节点文本显示 let showPipeText = true; // 控制管道文本显示 let junctionText = "pressure"; let pipeText = "flow"; const isAnimating = useRef(false); // 添加动画控制标志 // 防抖更新函数 const debouncedUpdateData = useRef( debounce(() => { if (tileJunctionDataBuffer.current.length > 0) { setJunctionData(tileJunctionDataBuffer.current); tileJunctionDataBuffer.current = []; } if (tilePipeDataBuffer.current.length > 0) { setPipeData(tilePipeDataBuffer.current); tilePipeDataBuffer.current = []; } }, 100) ); const setJunctionData = (newData: any[]) => { const uniqueNewData = newData.filter((item) => { if (!item || !item.id) return false; if (!junctionDataIds.current.has(item.id)) { junctionDataIds.current.add(item.id); return true; } return false; }); if (uniqueNewData.length > 0) { setJunctionDataState((prev) => [...prev, ...uniqueNewData]); } }; const setPipeData = (newData: any[]) => { const uniqueNewData = newData.filter((item) => { if (!item || !item.id) return false; if (!pipeDataIds.current.has(item.id)) { pipeDataIds.current.add(item.id); return true; } return false; }); if (uniqueNewData.length > 0) { setPipeDataState((prev) => [...prev, ...uniqueNewData]); } }; const setFrameData = async (queryTime: Date) => { const query_time = queryTime.toISOString(); console.log("Query Time:", query_time); try { // 定义需要查询的属性 const junctionProperties = junctionText; const pipeProperties = pipeText; // 同时查询节点和管道数据 const starttime = Date.now(); const [nodeResponse, linkResponse] = await Promise.all([ fetch( `${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}` ), fetch( `${backendUrl}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}` ), ]); const nodeRecords = await nodeResponse.json(); const linkRecords = await linkResponse.json(); // 将 nodeRecords 转换为 Map 以提高查找效率 const nodeMap: Map = new Map( nodeRecords.results.map((r: any) => [r.ID, r]) ); // 将 linkRecords 转换为 Map 以提高查找效率 const linkMap: Map = new Map( linkRecords.results.map((r: any) => [r.ID, r]) ); // 更新junctionData setJunctionDataState((prev) => prev.map((j) => { const record = nodeMap.get(j.id); if (record) { return { ...j, [junctionProperties]: record.value, }; } return j; }) ); // 更新pipeData setPipeDataState((prev) => prev.map((p) => { const record = linkMap.get(p.id); if (record) { return { ...p, flowFlag: pipeProperties === "flow" && record.value < 0 ? -1 : 1, path: pipeProperties === "flow" && record.value < 0 && p.flowFlag > 0 ? [...p.path].reverse() : p.path, [pipeProperties]: record.value, }; } return p; }) ); // 属性为 flow 时启动动画 if (pipeProperties === "flow") { isAnimating.current = true; } else { isAnimating.current = false; } const endtime = Date.now(); console.log("Data fetch and update time:", endtime - starttime, "ms"); } catch (error) { console.error("Error fetching data:", error); } }; useEffect(() => { if (!mapRef.current) return; // 添加 MVT 瓦片加载逻辑 const defaultFlatStyle: FlatStyleLike = { "stroke-width": 3, "stroke-color": "rgba(51, 153, 204, 0.9)", "circle-fill-color": "rgba(255,255,255,0.4)", "circle-stroke-color": "rgba(255,255,255,0.9)", "circle-radius": [ "interpolate", ["linear"], ["zoom"], 12, 1, // 在缩放级别 12 时,圆形半径为 1px 24, 12, // 在缩放级别 24 时,圆形半径为 12px ], }; const junctionSource = new VectorTileSource({ url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_junctions_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL format: new MVT(), projection: "EPSG:3857", }); const pipeSource = new VectorTileSource({ url: `${mapUrl}/gwc/service/tms/1.0.0/TJWater:geo_pipes_mat@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`, // 替换为你的 MVT 瓦片服务 URL format: new MVT(), projection: "EPSG:3857", }); // 缓存数据 junctionSource.on("tileloadend", (event) => { try { if (event.tile instanceof VectorTile) { const renderFeatures = event.tile.getFeatures(); const data = new Map(); renderFeatures.forEach((renderFeature) => { const props = renderFeature.getProperties(); const featureId = props.id; if (featureId && !junctionDataIds.current.has(featureId)) { const geometry = renderFeature.getGeometry(); if (geometry) { const coordinates = geometry.getFlatCoordinates(); const coordWGS84 = toLonLat(coordinates); data.set(featureId, { id: featureId, position: coordWGS84, elevation: props.elevation || 0, demand: props.demand || 0, }); } } }); const uniqueData = Array.from(data.values()); if (uniqueData.length > 0) { tileJunctionDataBuffer.current.push(...uniqueData); debouncedUpdateData.current(); } } } catch (error) { console.error("Junction tile load error:", error); } }); pipeSource.on("tileloadend", (event) => { try { if (event.tile instanceof VectorTile) { const renderFeatures = event.tile.getFeatures(); const data = new Map(); renderFeatures.forEach((renderFeature) => { try { const props = renderFeature.getProperties(); const featureId = props.id; if (featureId && !pipeDataIds.current.has(featureId)) { const geometry = renderFeature.getGeometry(); if (geometry) { const flatCoordinates = geometry.getFlatCoordinates(); const stride = geometry.getStride(); // 获取步长,通常为 2 // 重建为 LineString GeoJSON 格式的 coordinates: [[x1, y1], [x2, y2], ...] const lineCoords = []; for (let i = 0; i < flatCoordinates.length; i += stride) { lineCoords.push([ flatCoordinates[i], flatCoordinates[i + 1], ]); } const lineCoordsWGS84 = lineCoords.map((coord) => { const [lon, lat] = toLonLat(coord); return [lon, lat]; }); // 计算中点 const midPoint = center({ type: "LineString", coordinates: lineCoordsWGS84, }).geometry.coordinates; // 计算角度 let lineAngle = bearing( lineCoordsWGS84[0], lineCoordsWGS84[lineCoordsWGS84.length - 1] ); lineAngle = -lineAngle + 90; if (lineAngle < -90 || lineAngle > 90) { lineAngle += 180; } // 计算时间戳(可选) const numSegments = lineCoordsWGS84.length - 1; const timestamps = [0]; if (numSegments > 0) { for (let i = 1; i <= numSegments; i++) { timestamps.push((i / numSegments) * 10); } } data.set(featureId, { id: featureId, diameter: props.diameter || 0, path: lineCoordsWGS84, // 使用重建后的坐标 position: midPoint, angle: lineAngle, timestamps, }); } } } catch (geomError) { console.error("Geometry calculation error:", geomError); } }); const uniqueData = Array.from(data.values()); if (uniqueData.length > 0) { tilePipeDataBuffer.current.push(...uniqueData); debouncedUpdateData.current(); } } } catch (error) { console.error("Pipe tile load error:", error); } }); // WebGL 渲染优化显示 const junctionLayer = new WebGLVectorTileLayer({ source: junctionSource as any, // 使用 WebGL 渲染 style: defaultFlatStyle, extent: extent, // 设置图层范围 maxZoom: 24, minZoom: 12, properties: { name: "节点图层", // 设置图层名称 value: "junctions", type: "point", properties: [ { name: "需求量", value: "demand" }, { name: "海拔高度", value: "elevation" }, ], }, }); const pipeLayer = new WebGLVectorTileLayer({ source: pipeSource as any, // 使用 WebGL 渲染 style: defaultFlatStyle, extent: extent, // 设置图层范围 maxZoom: 24, minZoom: 12, properties: { name: "管道图层", // 设置图层名称 value: "pipes", type: "linestring", properties: [ { name: "直径", value: "diameter" }, { name: "粗糙度", value: "roughness" }, { name: "局部损失", value: "minor_loss" }, ], }, }); const map = new OlMap({ target: mapRef.current, view: new View({ projection: "EPSG:3857", }), // 图层依面、线、点、标注次序添加 layers: [pipeLayer, junctionLayer], controls: [], }); setMap(map); map.getView().fit(extent, { padding: [50, 50, 50, 50], // 添加一些内边距 duration: 1000, // 动画持续时间 }); // 初始化 deck.gl const deck = new Deck({ initialViewState: { longitude: 0, latitude: 0, zoom: 1, }, canvas: "deck-canvas", controller: false, // 由 OpenLayers 控制视图 layers: [], }); deckRef.current = deck; const deckLayer = new DeckLayer(deck); // deckLayer.setZIndex(1000); // 确保在最上层 map.addLayer(deckLayer); // 清理函数 return () => { map.setTarget(undefined); map.dispose(); deck.finalize(); }; }, []); // 当数据变化时,更新 deck.gl 图层 useEffect(() => { const deck = deckRef.current; if (!deck) return; // 如果 deck 实例还未创建,则退出 const newLayers = [ new TextLayer({ id: "junctionTextLayer", zIndex: 1000, data: showJunctionText ? junctionData : [], getPosition: (d: any) => d.position, fontFamily: "Monaco, monospace", getText: (d: any) => d[junctionText] ? (d[junctionText] as number).toFixed(3) : "", getSize: 12, getColor: [150, 150, 255], getAngle: 0, getTextAnchor: "middle", getAlignmentBaseline: "center", getPixelOffset: [0, -10], // --- 修改以下属性 --- // characterSet: "auto", // outlineWidth: 4, // outlineColor: [255, 255, 255, 255], // 设置为白色轮廓 }), new TextLayer({ id: "pipeTextLayer", zIndex: 1000, data: showPipeText ? pipeData : [], getPosition: (d: any) => d.position, fontFamily: "Monaco, monospace", getText: (d: any) => d[pipeText] ? (d[pipeText] as number).toFixed(3) : "", getSize: 14, getColor: [120, 128, 181], getAngle: (d: any) => d.angle || 0, getPixelOffset: [0, -8], getTextAnchor: "middle", getAlignmentBaseline: "bottom", // --- 修改以下属性 --- // characterSet: "auto", // outlineWidth: 5, // outlineColor: [255, 255, 255, 255], // 设置为白色轮廓 }), ]; deck.setProps({ layers: newLayers }); // 动画循环 const animate = () => { if (!deck || !isAnimating.current) return; // 添加检查,防止空数据或停止旧循环 // 动画总时长(秒) if (pipeData.length === 0) { requestAnimationFrame(animate); return; } const animationDuration = 10; // 缓冲时间(秒) const bufferTime = 2; // 完整循环周期 const loopLength = animationDuration + bufferTime; // 确保时间范围与你的时间戳数据匹配 const currentTime = (Date.now() / 1000) % loopLength; // (0,12) 之间循环 // console.log("Current Time:", currentTime); const waterflowLayer = new TripsLayer({ id: "waterflowLayer", data: pipeData, getPath: (d) => (isAnimating.current ? d.path : []), getTimestamps: (d) => { return d.timestamps; // 这些应该是与 currentTime 匹配的数值 }, getColor: [0, 220, 255], opacity: 0.8, widthMinPixels: 5, jointRounded: true, // 拐角变圆 // capRounded: true, // 端点变圆 trailLength: 2, // 水流尾迹淡出时间 currentTime: currentTime, }); // 获取当前除 waterflowLayer 之外的所有图层 const otherLayers = deck.props.layers.filter( (layer: any) => layer && layer.id !== "waterflowLayer" ); deck.setProps({ layers: [...otherLayers, waterflowLayer], }); // 继续请求动画帧,每帧执行一次函数 requestAnimationFrame(animate); }; animate(); }, [isAnimating, junctionData, pipeData]); // 启动时间更新interval useEffect(() => { intervalRef.current = setInterval(() => { setCurrentTime((prev) => new Date(prev.getTime() + 1800 * 1000)); }, 10 * 1000); return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, []); // 当currentTime改变时,获取数据 useEffect(() => { const fetchData = async () => { await setFrameData(currentTime); }; fetchData(); }, [currentTime]); return ( <>
); }; export default MapComponent;