增加渲染节点功能,优化工具操作和样式

This commit is contained in:
2026-05-18 15:44:36 +08:00
parent 03ca56d2a7
commit 45274955c6
7 changed files with 278 additions and 97 deletions
@@ -17,18 +17,14 @@ import {
ChevronRight,
FormatListBulleted,
} from "@mui/icons-material";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
import { VectorTile } from "ol";
import { FlatStyleLike } from "ol/style/flat";
import { useMap } from "@components/olmap/core/MapComponent";
import StyleLegend from "@components/olmap/core/Controls/StyleLegend";
import AnalysisParameters from "./AnalysisParameters";
import SchemeQuery from "./SchemeQuery";
import RecognitionResults from "./RecognitionResults";
import { applyJunctionAreaRender } from "./applyJunctionAreaRender";
import { getAreaColor } from "./utils";
import { LeakageResultDetail, LeakageSchemeRecord } from "./types";
import { config } from "@/config/config";
const TabPanel = ({
value,
@@ -82,101 +78,26 @@ const DMALeakDetectionPanel: React.FC = () => {
useEffect(() => {
if (!map) return;
const junctionLayer = map
.getAllLayers()
.find(
(layer) =>
layer instanceof WebGLVectorTileLayer && layer.get("value") === "junctions",
) as WebGLVectorTileLayer | undefined;
if (!junctionLayer) return;
const source = junctionLayer.getSource() as VectorTileSource;
if (!source) return;
if (!loadedResult || !loadedResult.node_area_map) {
junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
return;
}
const fallbackAreaIds = Array.from(
new Set(Object.values(loadedResult.node_area_map || {}).map(String)),
new Set(Object.values(loadedResult?.node_area_map ?? {}).map(String)),
);
const areaIds = (loadedResult.areas || []).length
? loadedResult.areas.map((area) => String(area.area_id))
const areaIds = (loadedResult?.areas ?? []).length
? (loadedResult?.areas ?? []).map((area) => String(area.area_id))
: fallbackAreaIds;
if (areaIds.length === 0) {
junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
return;
}
const areaIdToIndex = new Map<string, number>();
areaIds.forEach((areaId, index) => {
areaIdToIndex.set(areaId, index + 1);
});
const nodeAreaIndexMap = new Map<string, number>();
Object.entries(loadedResult.node_area_map || {}).forEach(([nodeId, areaId]) => {
const idx = areaIdToIndex.get(String(areaId));
if (idx !== undefined) {
nodeAreaIndexMap.set(String(nodeId), idx);
}
});
const applyFeatureAreaIndex = (renderFeature: any) => {
const featureId = String(renderFeature.get("id") ?? "");
const areaIndex = nodeAreaIndexMap.get(featureId);
if (areaIndex !== undefined) {
renderFeature.properties_[DMA_AREA_INDEX_PROPERTY] = areaIndex;
}
};
const sourceTiles = (source as any).sourceTiles_;
if (sourceTiles) {
Object.values(sourceTiles).forEach((vectorTile: any) => {
const renderFeatures = vectorTile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
renderFeatures.forEach((renderFeature: any) => {
applyFeatureAreaIndex(renderFeature);
});
});
}
const listener = (event: any) => {
try {
if (event.tile instanceof VectorTile) {
const renderFeatures = event.tile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
renderFeatures.forEach((renderFeature: any) => {
applyFeatureAreaIndex(renderFeature);
});
}
} catch (error) {
console.error("Error applying DMA area mapping:", error);
}
};
source.on("tileloadend", listener);
const fillCases: any[] = [];
areaIds.forEach((areaId, index) => {
fillCases.push(
["==", ["get", DMA_AREA_INDEX_PROPERTY], index + 1],
getAreaColor(areaId),
);
});
const defaultFillColor = String(config.MAP_DEFAULT_STYLE["circle-fill-color"]);
const defaultStrokeColor = String(
config.MAP_DEFAULT_STYLE["circle-stroke-color"],
const areaColors = Object.fromEntries(
areaIds.map((areaId) => [areaId, getAreaColor(areaId)]),
);
const dmaStyle: FlatStyleLike = {
...config.MAP_DEFAULT_STYLE,
"circle-fill-color": ["case", ...fillCases, defaultFillColor],
"circle-stroke-color": ["case", ...fillCases, defaultStrokeColor],
};
junctionLayer.setStyle(dmaStyle);
return () => {
source.un("tileloadend", listener);
junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
};
return applyJunctionAreaRender(
map,
{
nodeAreaMap: loadedResult?.node_area_map ?? {},
areaIds,
areaColors,
},
{ propertyKey: DMA_AREA_INDEX_PROPERTY },
);
}, [map, loadedResult]);
return (
@@ -0,0 +1,147 @@
import { Map as OlMap, VectorTile } from "ol";
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import VectorTileSource from "ol/source/VectorTile";
import { FlatStyleLike } from "ol/style/flat";
import { config } from "@/config/config";
import { getAreaColor } from "./utils";
const JUNCTION_LAYER_VALUE = "junctions";
const RENDER_OWNER_KEY = "junction-area-render-owner";
export type JunctionAreaRenderPayload = {
nodeAreaMap: Record<string, string>;
areaIds?: string[];
areaColors?: Record<string, string>;
};
type ApplyJunctionAreaRenderOptions = {
propertyKey?: string;
};
const DEFAULT_PROPERTY_KEY = "junction_area_render_index";
const getJunctionLayer = (map: OlMap) =>
map
.getAllLayers()
.find(
(layer) =>
layer instanceof WebGLVectorTileLayer &&
layer.get("value") === JUNCTION_LAYER_VALUE,
) as WebGLVectorTileLayer | undefined;
export const applyJunctionAreaRender = (
map: OlMap,
payload: JunctionAreaRenderPayload,
options: ApplyJunctionAreaRenderOptions = {},
) => {
const propertyKey = options.propertyKey ?? DEFAULT_PROPERTY_KEY;
const junctionLayer = getJunctionLayer(map);
if (!junctionLayer) {
return () => {};
}
const source = junctionLayer.getSource() as VectorTileSource | null;
if (!source) {
return () => {};
}
const ownerId = `${propertyKey}-${Date.now().toString(36)}-${Math.random()
.toString(36)
.slice(2, 8)}`;
const normalizedNodeAreaMap = Object.fromEntries(
Object.entries(payload.nodeAreaMap ?? {}).map(([nodeId, areaId]) => [
String(nodeId),
String(areaId),
]),
);
const areaIds = (
payload.areaIds?.length
? payload.areaIds
: Array.from(new Set(Object.values(normalizedNodeAreaMap)))
)
.map(String)
.filter(Boolean);
if (Object.keys(normalizedNodeAreaMap).length === 0 || areaIds.length === 0) {
junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
return () => {};
}
const areaIdToIndex = new Map<string, number>();
areaIds.forEach((areaId, index) => {
areaIdToIndex.set(areaId, index + 1);
});
const nodeAreaIndexMap = new Map<string, number>();
Object.entries(normalizedNodeAreaMap).forEach(([nodeId, areaId]) => {
const areaIndex = areaIdToIndex.get(areaId);
if (areaIndex !== undefined) {
nodeAreaIndexMap.set(nodeId, areaIndex);
}
});
const applyFeatureAreaIndex = (renderFeature: any) => {
const featureId = String(renderFeature.get("id") ?? "");
const areaIndex = nodeAreaIndexMap.get(featureId);
if (areaIndex !== undefined) {
renderFeature.properties_[propertyKey] = areaIndex;
}
};
const sourceTiles = (source as any).sourceTiles_;
if (sourceTiles) {
Object.values(sourceTiles).forEach((vectorTile: any) => {
const renderFeatures = vectorTile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
renderFeatures.forEach((renderFeature: any) => {
applyFeatureAreaIndex(renderFeature);
});
});
}
const listener = (event: any) => {
try {
if (!(event.tile instanceof VectorTile)) return;
const renderFeatures = event.tile.getFeatures();
if (!renderFeatures || renderFeatures.length === 0) return;
renderFeatures.forEach((renderFeature: any) => {
applyFeatureAreaIndex(renderFeature);
});
} catch (error) {
console.error("Error applying junction area render:", error);
}
};
source.on("tileloadend", listener);
const fillCases: any[] = [];
areaIds.forEach((areaId, index) => {
fillCases.push(
["==", ["get", propertyKey], index + 1],
payload.areaColors?.[areaId] ?? getAreaColor(areaId),
);
});
const defaultFillColor = String(config.MAP_DEFAULT_STYLE["circle-fill-color"]);
const defaultStrokeColor = String(
config.MAP_DEFAULT_STYLE["circle-stroke-color"],
);
junctionLayer.set(RENDER_OWNER_KEY, ownerId);
junctionLayer.setStyle({
...config.MAP_DEFAULT_STYLE,
"circle-fill-color": ["case", ...fillCases, defaultFillColor],
"circle-stroke-color": ["case", ...fillCases, defaultStrokeColor],
} as FlatStyleLike);
return () => {
source.un("tileloadend", listener);
if (junctionLayer.get(RENDER_OWNER_KEY) === ownerId) {
junctionLayer.unset(RENDER_OWNER_KEY, true);
junctionLayer.setStyle(config.MAP_DEFAULT_STYLE as FlatStyleLike);
}
};
};
+32 -3
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react";
import React, { useState, useEffect, useCallback, useRef } from "react";
import { useData, useMap } from "../MapComponent";
import ToolbarButton from "@/components/olmap/common/ToolbarButton";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
@@ -24,6 +24,7 @@ import StyleLegend from "./StyleLegend"; // 引入图例组件
import { handleMapClickSelectFeatures as mapClickSelectFeatures, queryFeaturesByIds } from "@/utils/mapQueryService";
import { useNotification } from "@refinedev/core";
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
import { applyJunctionAreaRender } from "@components/olmap/DMALeakDetection/applyJunctionAreaRender";
import { config } from "@/config/config";
import { apiFetch } from "@/lib/apiFetch";
@@ -81,8 +82,16 @@ const Toolbar: React.FC<ToolbarProps> = ({
startTime?: string;
endTime?: string;
} | null>(null);
const chatJunctionRenderCleanupRef = useRef<(() => void) | null>(null);
// Wire up chat tool actions (locate, view_history, view_scada)
const disposeChatJunctionRender = useCallback(() => {
chatJunctionRenderCleanupRef.current?.();
chatJunctionRenderCleanupRef.current = null;
}, []);
useEffect(() => () => disposeChatJunctionRender(), [disposeChatJunctionRender]);
// Wire up chat tool actions (locate, view_history, view_scada, render_junctions)
useChatToolActionHandler(
useCallback(
(action) => {
@@ -161,9 +170,29 @@ const Toolbar: React.FC<ToolbarProps> = ({
});
break;
}
case "render_junctions": {
disposeChatJunctionRender();
if (Object.keys(action.nodeAreaMap).length === 0) {
break;
}
if (map) {
chatJunctionRenderCleanupRef.current = applyJunctionAreaRender(
map,
{
nodeAreaMap: action.nodeAreaMap,
areaIds: action.areaIds,
areaColors: action.areaColors,
},
{ propertyKey: "chat_junction_render_index" },
);
}
break;
}
}
},
[map],
[disposeChatJunctionRender, map],
),
);