添加对比模式功能,优化地图组件
This commit is contained in:
@@ -1,158 +1,174 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useMemo, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
import { useMap } from "../MapComponent";
|
||||
import { useData, useMap } from "../MapComponent";
|
||||
import TileLayer from "ol/layer/Tile.js";
|
||||
import XYZ from "ol/source/XYZ.js";
|
||||
import Group from "ol/layer/Group";
|
||||
import mapboxOutdoors from "@assets/map/layers/mapbox-outdoors.png";
|
||||
import mapboxLight from "@assets/map/layers/mapbox-light.png";
|
||||
import mapboxSatellite from "@assets/map/layers/mapbox-satellite.png";
|
||||
import mapboxSatelliteStreet from "@assets/map/layers/mapbox-satellite-streets.png";
|
||||
import mapboxStreets from "@assets/map/layers/mapbox-streets.png";
|
||||
import clsx from "clsx";
|
||||
import Group from "ol/layer/Group";
|
||||
import { MAPBOX_TOKEN } from "@config/config";
|
||||
import { TIANDITU_TOKEN } from "@config/config";
|
||||
import { MAPBOX_TOKEN, TIANDITU_TOKEN } from "@config/config";
|
||||
import type { Map as OlMap } from "ol";
|
||||
|
||||
const INITIAL_LAYER = "mapbox-light";
|
||||
|
||||
const streetsLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
tileSize: 512,
|
||||
maxZoom: 20,
|
||||
projection: "EPSG:3857",
|
||||
attributions:
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
|
||||
}),
|
||||
});
|
||||
const lightMapLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
tileSize: 512,
|
||||
maxZoom: 20,
|
||||
projection: "EPSG:3857",
|
||||
attributions:
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
|
||||
}),
|
||||
});
|
||||
const satelliteLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
tileSize: 512,
|
||||
maxZoom: 20,
|
||||
projection: "EPSG:3857",
|
||||
attributions:
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
|
||||
}),
|
||||
});
|
||||
const satelliteStreetsLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
tileSize: 512,
|
||||
maxZoom: 20,
|
||||
projection: "EPSG:3857",
|
||||
attributions:
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>',
|
||||
}),
|
||||
});
|
||||
const createTileLayer = (url: string, attributions: string) =>
|
||||
new TileLayer({
|
||||
source: new XYZ({
|
||||
url,
|
||||
tileSize: 512,
|
||||
maxZoom: 20,
|
||||
projection: "EPSG:3857",
|
||||
attributions,
|
||||
}),
|
||||
});
|
||||
|
||||
const tiandituVectorLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituVectorAnnotationLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituImageLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituImageAnnotationLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituVectorLayerGroup = new Group({
|
||||
layers: [tiandituVectorLayer, tiandituVectorAnnotationLayer],
|
||||
});
|
||||
const tiandituImageLayerGroup = new Group({
|
||||
layers: [tiandituImageLayer, tiandituImageAnnotationLayer],
|
||||
});
|
||||
const baseLayers = [
|
||||
{
|
||||
id: "mapbox-light",
|
||||
name: "默认地图",
|
||||
layer: lightMapLayer,
|
||||
// layer: tiandituVectorLayerGroup,
|
||||
img: mapboxLight.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-satellite",
|
||||
name: "卫星地图",
|
||||
layer: satelliteLayer,
|
||||
// layer: tiandituImageLayerGroup,
|
||||
img: mapboxSatellite.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-satellite-streets",
|
||||
name: "卫星街道地图",
|
||||
layer: satelliteStreetsLayer,
|
||||
img: mapboxSatelliteStreet.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-streets",
|
||||
name: "街道地图",
|
||||
layer: streetsLayer,
|
||||
img: mapboxStreets.src,
|
||||
},
|
||||
];
|
||||
const createBaseLayerEntries = () => {
|
||||
const streetsLayer = createTileLayer(
|
||||
`https://api.mapbox.com/styles/v1/mapbox/streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>'
|
||||
);
|
||||
const lightMapLayer = createTileLayer(
|
||||
`https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>'
|
||||
);
|
||||
const satelliteLayer = createTileLayer(
|
||||
`https://api.mapbox.com/styles/v1/mapbox/satellite-v9/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>'
|
||||
);
|
||||
const satelliteStreetsLayer = createTileLayer(
|
||||
`https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/256/{z}/{x}/{y}@2x?access_token=${MAPBOX_TOKEN}`,
|
||||
'数据来源:<a href="https://www.mapbox.com/">Mapbox</a> & <a href="https://www.openstreetmap.org/">OpenStreetMap</a>'
|
||||
);
|
||||
|
||||
const tiandituVectorLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituVectorAnnotationLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituImageLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=img&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
const tiandituImageAnnotationLayer = new TileLayer({
|
||||
source: new XYZ({
|
||||
url: `https://t0.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${TIANDITU_TOKEN}`,
|
||||
projection: "EPSG:3857",
|
||||
attributions: '数据来源:<a href="https://www.tianditu.gov.cn/">天地图</a>',
|
||||
}),
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
id: "mapbox-light",
|
||||
name: "默认地图",
|
||||
layer: lightMapLayer,
|
||||
img: mapboxLight.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-satellite",
|
||||
name: "卫星地图",
|
||||
layer: satelliteLayer,
|
||||
img: mapboxSatellite.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-satellite-streets",
|
||||
name: "卫星街道地图",
|
||||
layer: satelliteStreetsLayer,
|
||||
img: mapboxSatelliteStreet.src,
|
||||
},
|
||||
{
|
||||
id: "mapbox-streets",
|
||||
name: "街道地图",
|
||||
layer: streetsLayer,
|
||||
img: mapboxStreets.src,
|
||||
},
|
||||
{
|
||||
id: "tianditu-vector",
|
||||
name: "天地图矢量",
|
||||
layer: new Group({
|
||||
layers: [tiandituVectorLayer, tiandituVectorAnnotationLayer],
|
||||
}),
|
||||
img: mapboxOutdoors.src,
|
||||
},
|
||||
{
|
||||
id: "tianditu-image",
|
||||
name: "天地图影像",
|
||||
layer: new Group({
|
||||
layers: [tiandituImageLayer, tiandituImageAnnotationLayer],
|
||||
}),
|
||||
img: mapboxSatellite.src,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const BaseLayers: React.FC = () => {
|
||||
const map = useMap();
|
||||
// 切换底图选项展开,控制显示和卸载
|
||||
const data = useData();
|
||||
const maps = useMemo(() => {
|
||||
if (data?.maps?.length) return data.maps;
|
||||
return map ? [map] : [];
|
||||
}, [data?.maps, map]);
|
||||
const layerSetsRef = useRef(new WeakMap<OlMap, ReturnType<typeof createBaseLayerEntries>>());
|
||||
const [isShow, setShow] = useState(false);
|
||||
const [isExpanded, setExpanded] = useState(false);
|
||||
// 快速切换底图
|
||||
const [activeId, setActiveId] = useState(INITIAL_LAYER);
|
||||
|
||||
// 初始化默认底图
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
// 添加所有底图至地图并根据 activeId 控制可见性
|
||||
baseLayers.forEach((layerInfo) => {
|
||||
const layers = map.getLayers().getArray();
|
||||
if (!layers.includes(layerInfo.layer)) {
|
||||
map.getLayers().insertAt(0, layerInfo.layer);
|
||||
maps.forEach((targetMap) => {
|
||||
let layerEntries = layerSetsRef.current.get(targetMap);
|
||||
if (!layerEntries) {
|
||||
layerEntries = createBaseLayerEntries();
|
||||
layerSetsRef.current.set(targetMap, layerEntries);
|
||||
}
|
||||
layerInfo.layer.setVisible(layerInfo.id === activeId);
|
||||
|
||||
layerEntries.forEach((layerInfo) => {
|
||||
const layers = targetMap.getLayers().getArray();
|
||||
if (!layers.includes(layerInfo.layer)) {
|
||||
targetMap.getLayers().insertAt(0, layerInfo.layer);
|
||||
}
|
||||
layerInfo.layer.setVisible(layerInfo.id === activeId);
|
||||
});
|
||||
});
|
||||
}, [map, activeId]);
|
||||
}, [activeId, maps]);
|
||||
|
||||
const changeMapLayers = (id: string) => {
|
||||
if (map) {
|
||||
// 根据 id 设置每个图层的可见性
|
||||
baseLayers.forEach(({ id: lid, layer }) => {
|
||||
layer.setVisible(lid === id);
|
||||
maps.forEach((targetMap) => {
|
||||
const layerEntries = layerSetsRef.current.get(targetMap);
|
||||
layerEntries?.forEach(({ id: layerId, layer }) => {
|
||||
layer.setVisible(layerId === id);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const baseLayers = useMemo(() => createBaseLayerEntries().map(({ id, name, img }) => ({
|
||||
id,
|
||||
name,
|
||||
img,
|
||||
})), []);
|
||||
|
||||
const handleQuickSwitch = () => {
|
||||
const nextId =
|
||||
activeId === baseLayers[0].id ? baseLayers[1].id : baseLayers[0].id;
|
||||
setActiveId(nextId);
|
||||
handleMapLayers(nextId);
|
||||
changeMapLayers(nextId);
|
||||
};
|
||||
|
||||
const handleMapLayers = (id: string) => {
|
||||
@@ -160,7 +176,6 @@ const BaseLayers: React.FC = () => {
|
||||
changeMapLayers(id);
|
||||
};
|
||||
|
||||
// 记录定时器,避免多次触发
|
||||
const hideTimer = React.useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const handleEnter = () => {
|
||||
@@ -217,7 +232,7 @@ const BaseLayers: React.FC = () => {
|
||||
{isExpanded && (
|
||||
<div
|
||||
className={clsx(
|
||||
"absolute flex right-24 bottom-0 w-90 h-25 bg-white rounded-xl drop-shadow-xl shadow-black transition-all duration-300",
|
||||
"absolute flex right-24 bottom-0 w-132 h-25 bg-white rounded-xl drop-shadow-xl shadow-black transition-all duration-300",
|
||||
isShow ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
onMouseEnter={handleEnter}
|
||||
@@ -226,7 +241,7 @@ const BaseLayers: React.FC = () => {
|
||||
{baseLayers.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
className="flex flex-auto flex-col justify-center items-center text-gray-500 text-xs"
|
||||
className="flex flex-auto flex-col justify-center items-center text-gray-500 text-xs"
|
||||
onClick={() => handleMapLayers(item.id)}
|
||||
>
|
||||
<Image
|
||||
|
||||
@@ -5,6 +5,7 @@ import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorTileLayer from "ol/layer/VectorTile";
|
||||
import { DeckLayer } from "@utils/layers";
|
||||
import type { Map as OlMap } from "ol";
|
||||
|
||||
// 定义统一的图层项接口
|
||||
interface LayerItem {
|
||||
@@ -30,8 +31,10 @@ const LAYER_ORDER = [
|
||||
const LayerControl: React.FC = () => {
|
||||
const map = useMap();
|
||||
const data = useData();
|
||||
const maps: OlMap[] = data?.maps?.length ? data.maps : map ? [map] : [];
|
||||
const [refreshKey, setRefreshKey] = useState(0);
|
||||
const deckLayer = data?.deckLayer;
|
||||
const deckLayers = data?.deckLayers ?? (deckLayer ? [deckLayer] : []);
|
||||
const isContourLayerAvailable = data?.isContourLayerAvailable;
|
||||
const isWaterflowLayerAvailable = data?.isWaterflowLayerAvailable;
|
||||
const setShowWaterflowLayer = data?.setShowWaterflowLayer;
|
||||
@@ -117,8 +120,16 @@ const LayerControl: React.FC = () => {
|
||||
|
||||
const handleVisibilityChange = (item: LayerItem, checked: boolean) => {
|
||||
if (item.type === "ol") {
|
||||
item.layerRef.setVisible(checked);
|
||||
} else if (item.type === "deck" && deckLayer) {
|
||||
maps.forEach((targetMap) => {
|
||||
targetMap
|
||||
.getAllLayers()
|
||||
.filter((layer) => layer.get("value") === item.id)
|
||||
.forEach((layer) => layer.setVisible(checked));
|
||||
});
|
||||
} else if (item.type === "deck" && deckLayers.length > 0) {
|
||||
deckLayers.forEach((targetDeckLayer) => {
|
||||
targetDeckLayer.setDeckLayerVisible(item.id, checked);
|
||||
});
|
||||
if (item.id === "junctionContourLayer") {
|
||||
setShowContourLayer && setShowContourLayer(checked);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import { FlatStyleLike } from "ol/style/flat";
|
||||
import { calculateClassification } from "@utils/breaks_classification";
|
||||
import { parseColor } from "@utils/parseColor";
|
||||
import { VectorTile } from "ol";
|
||||
import type { Map as OlMap } from "ol";
|
||||
import { useNotification } from "@refinedev/core";
|
||||
import { config } from "@/config/config";
|
||||
|
||||
@@ -182,6 +183,13 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
const data = useData();
|
||||
const currentJunctionCalData = data?.currentJunctionCalData;
|
||||
const currentPipeCalData = data?.currentPipeCalData;
|
||||
const compareJunctionCalData = data?.compareJunctionCalData;
|
||||
const comparePipeCalData = data?.comparePipeCalData;
|
||||
const compareMap = data?.compareMap;
|
||||
const activeMaps = useMemo<OlMap[]>(
|
||||
() => (data?.maps?.length ? data.maps : map ? [map] : []),
|
||||
[data?.maps, map]
|
||||
);
|
||||
const junctionText = data?.junctionText ?? "";
|
||||
const pipeText = data?.pipeText ?? "";
|
||||
const setShowJunctionTextLayer = data?.setShowJunctionTextLayer;
|
||||
@@ -229,6 +237,45 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
customColors: [],
|
||||
});
|
||||
|
||||
const getRenderLayersById = useCallback(
|
||||
(layerId: string) =>
|
||||
activeMaps.flatMap((targetMap) =>
|
||||
targetMap
|
||||
.getAllLayers()
|
||||
.filter((layer) => layer.get("value") === layerId)
|
||||
.filter((layer): layer is WebGLVectorTileLayer => layer instanceof WebGLVectorTileLayer)
|
||||
),
|
||||
[activeMaps]
|
||||
);
|
||||
|
||||
const getMapKey = useCallback((targetMap: OlMap, layerId: string) => {
|
||||
const mapUid = (targetMap as unknown as { ol_uid?: string }).ol_uid || "map";
|
||||
return `${mapUid}:${layerId}`;
|
||||
}, []);
|
||||
|
||||
const getDataForMap = useCallback(
|
||||
(targetMap: OlMap, layerId: string) => {
|
||||
if (layerId === "junctions") {
|
||||
return targetMap === compareMap
|
||||
? compareJunctionCalData || []
|
||||
: currentJunctionCalData || [];
|
||||
}
|
||||
if (layerId === "pipes") {
|
||||
return targetMap === compareMap
|
||||
? comparePipeCalData || []
|
||||
: currentPipeCalData || [];
|
||||
}
|
||||
return [];
|
||||
},
|
||||
[
|
||||
compareJunctionCalData,
|
||||
compareMap,
|
||||
comparePipeCalData,
|
||||
currentJunctionCalData,
|
||||
currentPipeCalData,
|
||||
]
|
||||
);
|
||||
|
||||
const getDefaultCustomColors = (
|
||||
segments: number,
|
||||
existingColors: string[] = []
|
||||
@@ -613,13 +660,10 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
return;
|
||||
}
|
||||
const styleConfig = layerStyleConfig.styleConfig;
|
||||
const renderLayer = renderLayers.filter((layer) => {
|
||||
return layer.get("value") === layerStyleConfig.layerId;
|
||||
})[0];
|
||||
const targetLayers = getRenderLayersById(layerStyleConfig.layerId);
|
||||
const renderLayer = targetLayers[0];
|
||||
if (!renderLayer || !styleConfig?.property) return;
|
||||
const layerType: string = renderLayer?.get("type");
|
||||
const source = renderLayer.getSource();
|
||||
if (!source) return;
|
||||
const layerType: string = renderLayer.get("type");
|
||||
|
||||
const breaksLength = breaks.length;
|
||||
// 根据 breaks 计算每个分段的颜色,线条粗细
|
||||
@@ -757,7 +801,9 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
dynamicStyle["circle-stroke-width"] = 2;
|
||||
}
|
||||
// 应用样式到图层
|
||||
renderLayer.setStyle(dynamicStyle);
|
||||
targetLayers.forEach((targetLayer) => {
|
||||
targetLayer.setStyle(dynamicStyle);
|
||||
});
|
||||
// 用初始化时的样式配置更新图例配置,避免覆盖已有的图例名称和属性
|
||||
const layerId = renderLayer.get("value");
|
||||
const initLayerStyleState = layerStyleStates.find(
|
||||
@@ -844,10 +890,12 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
if (!selectedRenderLayer) return;
|
||||
// 重置 WebGL 图层样式
|
||||
const defaultFlatStyle: FlatStyleLike = config.MAP_DEFAULT_STYLE;
|
||||
selectedRenderLayer.setStyle(defaultFlatStyle);
|
||||
const layerId = selectedRenderLayer.get("value");
|
||||
getRenderLayersById(layerId).forEach((targetLayer) => {
|
||||
targetLayer.setStyle(defaultFlatStyle);
|
||||
});
|
||||
|
||||
// 删除对应图层的样式状态,从而移除图例显示
|
||||
const layerId = selectedRenderLayer.get("value");
|
||||
if (layerId !== undefined) {
|
||||
setLayerStyleStates((prev) =>
|
||||
prev.filter((state) => state.layerId !== layerId)
|
||||
@@ -870,11 +918,15 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
}
|
||||
};
|
||||
// 更新当前 VectorTileSource 中的所有缓冲要素属性
|
||||
const updateVectorTileSource = (property: string, data: any[]) => {
|
||||
if (!map) return;
|
||||
const vectorTileSources = map
|
||||
const updateVectorTileSource = (
|
||||
targetMap: OlMap,
|
||||
layerId: string,
|
||||
property: string,
|
||||
data: any[]
|
||||
) => {
|
||||
const vectorTileSources = targetMap
|
||||
.getAllLayers()
|
||||
.filter((layer) => layer instanceof WebGLVectorTileLayer)
|
||||
.filter((layer) => layer.get("value") === layerId)
|
||||
.map((layer) => layer.getSource() as VectorTileSource)
|
||||
.filter((source) => source);
|
||||
|
||||
@@ -911,16 +963,16 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
};
|
||||
// 新增事件,监听 VectorTileSource 的 tileloadend 事件,为新增瓦片数据动态更新要素属性
|
||||
const tileLoadListenersRef = useRef<
|
||||
Map<VectorTileSource, (event: any) => void>
|
||||
Map<string, { source: VectorTileSource; listener: (event: any) => void }>
|
||||
>(new Map());
|
||||
|
||||
const attachVectorTileSourceLoadedEvent = (
|
||||
targetMap: OlMap,
|
||||
layerId: string,
|
||||
property: string,
|
||||
data: any[]
|
||||
) => {
|
||||
if (!map) return;
|
||||
const vectorTileSource = map
|
||||
const vectorTileSource = targetMap
|
||||
.getAllLayers()
|
||||
.filter((layer) => layer.get("value") === layerId)
|
||||
.map((layer) => layer.getSource() as VectorTileSource)
|
||||
@@ -956,24 +1008,25 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const listenerKey = getMapKey(targetMap, layerId);
|
||||
vectorTileSource.on("tileloadend", listener);
|
||||
tileLoadListenersRef.current.set(vectorTileSource, listener);
|
||||
tileLoadListenersRef.current.set(listenerKey, {
|
||||
source: vectorTileSource,
|
||||
listener,
|
||||
});
|
||||
};
|
||||
// 新增函数:取消对应 layerId 已添加的 on 事件
|
||||
const removeVectorTileSourceLoadedEvent = (layerId: string) => {
|
||||
if (!map) return;
|
||||
const vectorTileSource = map
|
||||
.getAllLayers()
|
||||
.filter((layer) => layer.get("value") === layerId)
|
||||
.map((layer) => layer.getSource() as VectorTileSource)
|
||||
.filter((source) => source)[0];
|
||||
if (!vectorTileSource) return;
|
||||
const listener = tileLoadListenersRef.current.get(vectorTileSource);
|
||||
if (listener) {
|
||||
vectorTileSource.un("tileloadend", listener);
|
||||
tileLoadListenersRef.current.delete(vectorTileSource);
|
||||
}
|
||||
};
|
||||
const removeVectorTileSourceLoadedEvent = useCallback(
|
||||
(targetMap: OlMap, layerId: string) => {
|
||||
const listenerKey = getMapKey(targetMap, layerId);
|
||||
const listenerState = tileLoadListenersRef.current.get(listenerKey);
|
||||
if (listenerState) {
|
||||
listenerState.source.un("tileloadend", listenerState.listener);
|
||||
tileLoadListenersRef.current.delete(listenerKey);
|
||||
}
|
||||
},
|
||||
[getMapKey]
|
||||
);
|
||||
|
||||
// 监听数据变化,重新应用样式。由样式应用按钮触发,或由数据变化触发
|
||||
useEffect(() => {
|
||||
@@ -998,20 +1051,24 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
);
|
||||
|
||||
if (isElevation) {
|
||||
removeVectorTileSourceLoadedEvent("junctions");
|
||||
activeMaps.forEach((targetMap) => {
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "junctions");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentJunctionCalData) return;
|
||||
// 更新现有的 VectorTileSource
|
||||
updateVectorTileSource(junctionText, currentJunctionCalData);
|
||||
// 移除旧的监听器,并添加新的监听器
|
||||
removeVectorTileSourceLoadedEvent("junctions");
|
||||
attachVectorTileSourceLoadedEvent(
|
||||
"junctions",
|
||||
junctionText,
|
||||
currentJunctionCalData
|
||||
);
|
||||
activeMaps.forEach((targetMap) => {
|
||||
const targetData = getDataForMap(targetMap, "junctions");
|
||||
if (!targetData || targetData.length === 0) return;
|
||||
updateVectorTileSource(targetMap, "junctions", junctionText, targetData);
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "junctions");
|
||||
attachVectorTileSourceLoadedEvent(
|
||||
targetMap,
|
||||
"junctions",
|
||||
junctionText,
|
||||
targetData
|
||||
);
|
||||
});
|
||||
};
|
||||
const updatePipeStyle = () => {
|
||||
const pipeStyleConfigState = layerStyleStates.find(
|
||||
@@ -1023,16 +1080,24 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
applyClassificationStyle("pipes", pipeStyleConfigState?.styleConfig);
|
||||
|
||||
if (isDiameter) {
|
||||
removeVectorTileSourceLoadedEvent("pipes");
|
||||
activeMaps.forEach((targetMap) => {
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "pipes");
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentPipeCalData) return;
|
||||
// 更新现有的 VectorTileSource
|
||||
updateVectorTileSource(pipeText, currentPipeCalData);
|
||||
// 移除旧的监听器,并添加新的监听器
|
||||
removeVectorTileSourceLoadedEvent("pipes");
|
||||
attachVectorTileSourceLoadedEvent("pipes", pipeText, currentPipeCalData);
|
||||
activeMaps.forEach((targetMap) => {
|
||||
const targetData = getDataForMap(targetMap, "pipes");
|
||||
if (!targetData || targetData.length === 0) return;
|
||||
updateVectorTileSource(targetMap, "pipes", pipeText, targetData);
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "pipes");
|
||||
attachVectorTileSourceLoadedEvent(
|
||||
targetMap,
|
||||
"pipes",
|
||||
pipeText,
|
||||
targetData
|
||||
);
|
||||
});
|
||||
};
|
||||
if (isUserTrigger) {
|
||||
if (selectedRenderLayer?.get("value") === "junctions") {
|
||||
@@ -1060,10 +1125,14 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
updatePipeStyle();
|
||||
}
|
||||
if (!applyJunctionStyle) {
|
||||
removeVectorTileSourceLoadedEvent("junctions");
|
||||
activeMaps.forEach((targetMap) => {
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "junctions");
|
||||
});
|
||||
}
|
||||
if (!applyPipeStyle) {
|
||||
removeVectorTileSourceLoadedEvent("pipes");
|
||||
activeMaps.forEach((targetMap) => {
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "pipes");
|
||||
});
|
||||
}
|
||||
// This effect is intentionally driven by explicit style triggers and data snapshots.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -1073,8 +1142,20 @@ const StyleEditorPanel: React.FC<StyleEditorPanelProps> = ({
|
||||
applyPipeStyle,
|
||||
currentJunctionCalData,
|
||||
currentPipeCalData,
|
||||
compareJunctionCalData,
|
||||
comparePipeCalData,
|
||||
activeMaps,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
activeMaps.forEach((targetMap) => {
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "junctions");
|
||||
removeVectorTileSourceLoadedEvent(targetMap, "pipes");
|
||||
});
|
||||
};
|
||||
}, [activeMaps, removeVectorTileSourceLoadedEvent]);
|
||||
|
||||
// 获取地图中的矢量图层,用于选择图层选项
|
||||
useEffect(() => {
|
||||
if (!map) return;
|
||||
|
||||
@@ -63,6 +63,9 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
const setSelectedDate = data?.setSelectedDate ?? NOOP_SET_SELECTED_DATE;
|
||||
const setCurrentJunctionCalData = data?.setCurrentJunctionCalData;
|
||||
const setCurrentPipeCalData = data?.setCurrentPipeCalData;
|
||||
const setCompareJunctionCalData = data?.setCompareJunctionCalData;
|
||||
const setComparePipeCalData = data?.setComparePipeCalData;
|
||||
const isCompareMode = data?.isCompareMode ?? false;
|
||||
const junctionText = data?.junctionText ?? "";
|
||||
const pipeText = data?.pipeText ?? "";
|
||||
const { open } = useNotification();
|
||||
@@ -94,100 +97,209 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
// 添加防抖引用
|
||||
const debounceRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const updateDataStates = useCallback((nodeResults: any[], linkResults: any[]) => {
|
||||
if (setCurrentJunctionCalData) {
|
||||
setCurrentJunctionCalData(nodeResults);
|
||||
} else {
|
||||
console.log("setCurrentJunctionCalData is undefined");
|
||||
}
|
||||
if (setCurrentPipeCalData) {
|
||||
setCurrentPipeCalData(linkResults);
|
||||
} else {
|
||||
console.log("setCurrentPipeCalData is undefined");
|
||||
}
|
||||
}, [setCurrentJunctionCalData, setCurrentPipeCalData]);
|
||||
const updateDataStates = useCallback(
|
||||
(
|
||||
nodeResults: any[],
|
||||
linkResults: any[],
|
||||
target: "primary" | "compare" = "primary"
|
||||
) => {
|
||||
const setNodeData =
|
||||
target === "compare"
|
||||
? setCompareJunctionCalData
|
||||
: setCurrentJunctionCalData;
|
||||
const setLinkData =
|
||||
target === "compare" ? setComparePipeCalData : setCurrentPipeCalData;
|
||||
|
||||
const fetchFrameData = useCallback(async (
|
||||
queryTime: Date,
|
||||
junctionProperties: string,
|
||||
pipeProperties: string,
|
||||
schemeName: string,
|
||||
schemeType: string,
|
||||
) => {
|
||||
const query_time = queryTime.toISOString();
|
||||
let nodeRecords: any = { results: [] };
|
||||
let linkRecords: any = { results: [] };
|
||||
const requests: Promise<Response>[] = [];
|
||||
let nodePromise: Promise<any> | null = null;
|
||||
let linkPromise: Promise<any> | null = null;
|
||||
// 检查node缓存
|
||||
if (junctionProperties !== "" && junctionProperties !== "elevation") {
|
||||
const nodeCacheKey = `${query_time}_${junctionProperties}_${schemeName}_${schemeType}`;
|
||||
if (nodeCacheRef.current.has(nodeCacheKey)) {
|
||||
nodeRecords = nodeCacheRef.current.get(nodeCacheKey)!;
|
||||
} else {
|
||||
disableDateSelection && schemeName
|
||||
? (nodePromise = apiFetch(
|
||||
// `${config.BACKEND_URL}/queryallschemerecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}&schemename=${schemeName}`
|
||||
`${config.BACKEND_URL}/api/v1/scheme/query/by-scheme-time-property?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=node&property=${junctionProperties}`,
|
||||
))
|
||||
: (nodePromise = apiFetch(
|
||||
// `${config.BACKEND_URL}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=node&property=${junctionProperties}`
|
||||
`${config.BACKEND_URL}/api/v1/realtime/query/by-time-property?query_time=${query_time}&type=node&property=${junctionProperties}`,
|
||||
));
|
||||
requests.push(nodePromise);
|
||||
setNodeData?.(nodeResults);
|
||||
setLinkData?.(linkResults);
|
||||
},
|
||||
[
|
||||
setCompareJunctionCalData,
|
||||
setComparePipeCalData,
|
||||
setCurrentJunctionCalData,
|
||||
setCurrentPipeCalData,
|
||||
]
|
||||
);
|
||||
|
||||
const buildCacheKey = useCallback(
|
||||
(
|
||||
queryTime: string,
|
||||
property: string,
|
||||
sourceType: "scheme" | "realtime",
|
||||
resultType: "node" | "link",
|
||||
targetSchemeName: string,
|
||||
targetSchemeType: string
|
||||
) =>
|
||||
[
|
||||
queryTime,
|
||||
sourceType,
|
||||
resultType,
|
||||
property,
|
||||
targetSchemeName || "default",
|
||||
targetSchemeType || "default",
|
||||
].join("::"),
|
||||
[]
|
||||
);
|
||||
|
||||
const fetchDataBySource = useCallback(
|
||||
async ({
|
||||
queryTime,
|
||||
junctionProperties,
|
||||
pipeProperties,
|
||||
sourceType,
|
||||
target,
|
||||
schemeName,
|
||||
schemeType,
|
||||
}: {
|
||||
queryTime: Date;
|
||||
junctionProperties: string;
|
||||
pipeProperties: string;
|
||||
sourceType: "scheme" | "realtime";
|
||||
target: "primary" | "compare";
|
||||
schemeName?: string;
|
||||
schemeType?: string;
|
||||
}) => {
|
||||
const query_time = queryTime.toISOString();
|
||||
let nodeRecords: any = { results: [] };
|
||||
let linkRecords: any = { results: [] };
|
||||
const requests: Promise<Response>[] = [];
|
||||
let nodePromise: Promise<Response> | null = null;
|
||||
let linkPromise: Promise<Response> | null = null;
|
||||
|
||||
if (junctionProperties !== "" && junctionProperties !== "elevation") {
|
||||
const nodeCacheKey = buildCacheKey(
|
||||
query_time,
|
||||
junctionProperties,
|
||||
sourceType,
|
||||
"node",
|
||||
schemeName || "",
|
||||
schemeType || ""
|
||||
);
|
||||
if (nodeCacheRef.current.has(nodeCacheKey)) {
|
||||
nodeRecords = nodeCacheRef.current.get(nodeCacheKey)!;
|
||||
} else {
|
||||
nodePromise =
|
||||
sourceType === "scheme" && schemeName
|
||||
? apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/scheme/query/by-scheme-time-property?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=node&property=${junctionProperties}`
|
||||
)
|
||||
: apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/realtime/query/by-time-property?query_time=${query_time}&type=node&property=${junctionProperties}`
|
||||
);
|
||||
requests.push(nodePromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理特殊属性名称
|
||||
if (pipeProperties === "unit_headloss") pipeProperties = "headloss";
|
||||
|
||||
// 检查link缓存
|
||||
if (pipeProperties !== "" && pipeProperties !== "diameter") {
|
||||
const linkCacheKey = `${query_time}_${pipeProperties}_${schemeName}_${schemeType}`;
|
||||
if (linkCacheRef.current.has(linkCacheKey)) {
|
||||
linkRecords = linkCacheRef.current.get(linkCacheKey)!;
|
||||
} else {
|
||||
disableDateSelection && schemeName
|
||||
? (linkPromise = apiFetch(
|
||||
// `${config.BACKEND_URL}/queryallschemerecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}&schemename=${schemeName}`
|
||||
`${config.BACKEND_URL}/api/v1/scheme/query/by-scheme-time-property?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=link&property=${pipeProperties}`,
|
||||
))
|
||||
: (linkPromise = apiFetch(
|
||||
// `${config.BACKEND_URL}/queryallrecordsbytimeproperty/?querytime=${query_time}&type=link&property=${pipeProperties}`
|
||||
`${config.BACKEND_URL}/api/v1/realtime/query/by-time-property?query_time=${query_time}&type=link&property=${pipeProperties}`,
|
||||
));
|
||||
requests.push(linkPromise);
|
||||
const normalizedPipeProperties =
|
||||
pipeProperties === "unit_headloss" ? "headloss" : pipeProperties;
|
||||
|
||||
if (normalizedPipeProperties !== "" && normalizedPipeProperties !== "diameter") {
|
||||
const linkCacheKey = buildCacheKey(
|
||||
query_time,
|
||||
normalizedPipeProperties,
|
||||
sourceType,
|
||||
"link",
|
||||
schemeName || "",
|
||||
schemeType || ""
|
||||
);
|
||||
if (linkCacheRef.current.has(linkCacheKey)) {
|
||||
linkRecords = linkCacheRef.current.get(linkCacheKey)!;
|
||||
} else {
|
||||
linkPromise =
|
||||
sourceType === "scheme" && schemeName
|
||||
? apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/scheme/query/by-scheme-time-property?scheme_type=${schemeType}&scheme_name=${schemeName}&query_time=${query_time}&type=link&property=${normalizedPipeProperties}`
|
||||
)
|
||||
: apiFetch(
|
||||
`${config.BACKEND_URL}/api/v1/realtime/query/by-time-property?query_time=${query_time}&type=link&property=${normalizedPipeProperties}`
|
||||
);
|
||||
requests.push(linkPromise);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 等待所有有效请求
|
||||
const responses = await Promise.all(requests);
|
||||
const responses = await Promise.all(requests);
|
||||
|
||||
if (nodePromise) {
|
||||
const nodeResponse = responses.shift()!;
|
||||
if (!nodeResponse.ok)
|
||||
throw new Error(`Node fetch failed: ${nodeResponse.status}`);
|
||||
nodeRecords = await nodeResponse.json();
|
||||
// 缓存数据(修复键以包含 schemeName)
|
||||
nodeCacheRef.current.set(
|
||||
`${query_time}_${junctionProperties}_${schemeName}_${schemeType}`,
|
||||
nodeRecords || [],
|
||||
);
|
||||
}
|
||||
if (linkPromise) {
|
||||
const linkResponse = responses.shift()!;
|
||||
if (!linkResponse.ok)
|
||||
throw new Error(`Link fetch failed: ${linkResponse.status}`);
|
||||
linkRecords = await linkResponse.json();
|
||||
// 缓存数据(修复键以包含 schemeName)
|
||||
linkCacheRef.current.set(
|
||||
`${query_time}_${pipeProperties}_${schemeName}_${schemeType}`,
|
||||
linkRecords || [],
|
||||
);
|
||||
}
|
||||
// 更新状态
|
||||
updateDataStates(nodeRecords.results || [], linkRecords.results || []);
|
||||
}, [disableDateSelection, updateDataStates]);
|
||||
if (nodePromise) {
|
||||
const nodeResponse = responses.shift()!;
|
||||
if (!nodeResponse.ok) {
|
||||
throw new Error(`Node fetch failed: ${nodeResponse.status}`);
|
||||
}
|
||||
nodeRecords = await nodeResponse.json();
|
||||
nodeCacheRef.current.set(
|
||||
buildCacheKey(
|
||||
query_time,
|
||||
junctionProperties,
|
||||
sourceType,
|
||||
"node",
|
||||
schemeName || "",
|
||||
schemeType || ""
|
||||
),
|
||||
nodeRecords || []
|
||||
);
|
||||
}
|
||||
|
||||
if (linkPromise) {
|
||||
const linkResponse = responses.shift()!;
|
||||
if (!linkResponse.ok) {
|
||||
throw new Error(`Link fetch failed: ${linkResponse.status}`);
|
||||
}
|
||||
linkRecords = await linkResponse.json();
|
||||
linkCacheRef.current.set(
|
||||
buildCacheKey(
|
||||
query_time,
|
||||
normalizedPipeProperties,
|
||||
sourceType,
|
||||
"link",
|
||||
schemeName || "",
|
||||
schemeType || ""
|
||||
),
|
||||
linkRecords || []
|
||||
);
|
||||
}
|
||||
|
||||
updateDataStates(nodeRecords.results || [], linkRecords.results || [], target);
|
||||
},
|
||||
[buildCacheKey, updateDataStates]
|
||||
);
|
||||
|
||||
const fetchFrameData = useCallback(
|
||||
async (
|
||||
queryTime: Date,
|
||||
junctionProperties: string,
|
||||
pipeProperties: string,
|
||||
schemeName: string,
|
||||
schemeType: string
|
||||
) => {
|
||||
const primarySourceType =
|
||||
disableDateSelection && schemeName ? "scheme" : "realtime";
|
||||
const tasks = [
|
||||
fetchDataBySource({
|
||||
queryTime,
|
||||
junctionProperties,
|
||||
pipeProperties,
|
||||
sourceType: primarySourceType,
|
||||
target: "primary",
|
||||
schemeName,
|
||||
schemeType,
|
||||
}),
|
||||
];
|
||||
|
||||
if (isCompareMode && disableDateSelection && schemeName) {
|
||||
tasks.push(
|
||||
fetchDataBySource({
|
||||
queryTime,
|
||||
junctionProperties,
|
||||
pipeProperties,
|
||||
sourceType: "realtime",
|
||||
target: "compare",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(tasks);
|
||||
},
|
||||
[disableDateSelection, fetchDataBySource, isCompareMode]
|
||||
);
|
||||
|
||||
// 时间刻度数组 (每5分钟一个刻度)
|
||||
const timeMarks = Array.from({ length: 288 }, (_, i) => ({
|
||||
@@ -453,9 +565,9 @@ const Timeline: React.FC<TimelineProps> = ({
|
||||
if (!cacheRef.current) return;
|
||||
const cacheKeys = Array.from(cacheRef.current.keys());
|
||||
cacheKeys.forEach((key) => {
|
||||
const keyParts = key.split("_");
|
||||
const cacheDate = keyParts[0].split("T")[0];
|
||||
const cacheTimeStr = keyParts[0].split("T")[1];
|
||||
const cacheTimeKey = key.split("::")[0];
|
||||
const cacheDate = cacheTimeKey.split("T")[0];
|
||||
const cacheTimeStr = cacheTimeKey.split("T")[1];
|
||||
|
||||
if (cacheDate === dateStr && cacheTimeStr) {
|
||||
const [hours, minutes] = cacheTimeStr.split(":");
|
||||
|
||||
@@ -5,6 +5,7 @@ import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||
import PaletteOutlinedIcon from "@mui/icons-material/PaletteOutlined";
|
||||
import QueryStatsOutlinedIcon from "@mui/icons-material/QueryStatsOutlined";
|
||||
import CompareArrowsOutlinedIcon from "@mui/icons-material/CompareArrowsOutlined";
|
||||
import PropertyPanel from "./PropertyPanel"; // 引入属性面板组件
|
||||
import DrawPanel from "./DrawPanel"; // 引入绘图面板组件
|
||||
import HistoryDataPanel from "./HistoryDataPanel"; // 引入绘图面板组件
|
||||
@@ -34,12 +35,14 @@ interface ToolbarProps {
|
||||
queryType?: string; // 可选的查询类型参数
|
||||
schemeType?: string; // 可选的方案类型参数
|
||||
HistoryPanel?: React.FC<any>; // 可选的自定义历史数据面板
|
||||
enableCompare?: boolean;
|
||||
}
|
||||
const Toolbar: React.FC<ToolbarProps> = ({
|
||||
hiddenButtons,
|
||||
queryType,
|
||||
schemeType,
|
||||
HistoryPanel,
|
||||
enableCompare = false,
|
||||
}) => {
|
||||
const map = useMap();
|
||||
const data = useData();
|
||||
@@ -55,6 +58,17 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
const currentTime = data?.currentTime;
|
||||
const selectedDate = data?.selectedDate;
|
||||
const schemeName = data?.schemeName;
|
||||
const isCompareMode = data?.isCompareMode ?? false;
|
||||
const toggleCompareMode = data?.toggleCompareMode;
|
||||
const canToggleCompare = Boolean(
|
||||
enableCompare && (isCompareMode || (queryType === "scheme" && schemeName)),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!enableCompare && isCompareMode) {
|
||||
toggleCompareMode?.();
|
||||
}
|
||||
}, [enableCompare, isCompareMode, toggleCompareMode]);
|
||||
|
||||
// Chat tool action → direct featureInfos override (bypasses OL Feature lookup)
|
||||
const [chatPanelFeatureInfos, setChatPanelFeatureInfos] = useState<
|
||||
@@ -853,6 +867,15 @@ const Toolbar: React.FC<ToolbarProps> = ({
|
||||
onClick={() => handleToolClick("style")}
|
||||
/>
|
||||
)}
|
||||
{enableCompare && (
|
||||
<ToolbarButton
|
||||
icon={<CompareArrowsOutlinedIcon />}
|
||||
name={isCompareMode ? "关闭对比" : "双屏对比"}
|
||||
isActive={isCompareMode}
|
||||
onClick={() => toggleCompareMode?.()}
|
||||
disabled={!canToggleCompare}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{showPropertyPanel && <PropertyPanel {...getFeatureProperties()} />}
|
||||
{showDrawPanel && map && <DrawPanel />}
|
||||
|
||||
Reference in New Issue
Block a user