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; areaIds?: string[]; areaColors?: Record; }; 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(); areaIds.forEach((areaId, index) => { areaIdToIndex.set(areaId, index + 1); }); const nodeAreaIndexMap = new Map(); 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); } }; };