import { config } from "@/config/config"; import { along, lineString, length, toMercator } from "@turf/turf"; import type { FeatureLike } from "ol/Feature"; import MVT from "ol/format/MVT"; import { Point } from "ol/geom"; import type BaseLayer from "ol/layer/Base"; import VectorLayer from "ol/layer/Vector"; import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile"; import { toLonLat } from "ol/proj"; import GeoJSON from "ol/format/GeoJSON"; import VectorSource from "ol/source/Vector"; import VectorTileSource from "ol/source/VectorTile"; import { Icon, Style } from "ol/style"; import type { FlatStyleLike } from "ol/style/flat"; import { markMapResourcePersistent } from "./mapLifecycle"; type MapExtent = [number, number, number, number]; type CreateOperationalMapSourcesOptions = { mapUrl: string; workspace: string; }; type CreateOperationalLayersOptions = CreateOperationalMapSourcesOptions & { extent: MapExtent; persistent?: boolean; sources?: OperationalMapSources; }; export type OperationalMapSources = { junctions: VectorTileSource; pipes: VectorTileSource; valves: VectorTileSource; reservoirs: VectorSource; pumps: VectorSource; tanks: VectorSource; scada: VectorSource; }; const defaultFlatStyle = config.MAP_DEFAULT_STYLE as FlatStyleLike; const valveStyle = { "icon-src": "/icons/valve.svg", "icon-scale": 0.1, }; const createIconStyle = (src: string, scale = 0.1) => new Style({ image: new Icon({ src, scale, anchor: [0.5, 0.5] }) }); const scadaStyle = (feature: FeatureLike) => createIconStyle( feature.get("type") === "pipe_flow" ? "/icons/scada_flow.svg" : "/icons/scada_pressure.svg", ); const pumpStyle = (feature: FeatureLike) => { const geometry = feature.getGeometry(); if (!geometry || geometry.getType() !== "LineString") return []; const coordinates = (geometry as any) .getCoordinates() .map((coordinate: number[]) => toLonLat(coordinate)); if (coordinates.length < 2) return []; const featureLine = lineString(coordinates); const midpoint = along(featureLine, length(featureLine) / 2).geometry .coordinates; return [ new Style({ geometry: new Point(toMercator(midpoint)), image: new Icon({ src: "/icons/pump.svg", scale: 0.12, anchor: [0.5, 0.5], }), }), ]; }; const pointProperties = [ { name: "高程", value: "elevation" }, { name: "实际需水量", value: "actual_demand" }, { name: "水头", value: "total_head" }, { name: "压力", value: "pressure" }, { name: "水质", value: "quality" }, ]; const pipeProperties = [ { name: "管径", value: "diameter" }, { name: "流量", value: "flow" }, { name: "摩阻系数", value: "friction" }, { name: "水头损失", value: "headloss" }, { name: "单位水头损失", value: "unit_headloss" }, { name: "水质", value: "quality" }, { name: "反应速率", value: "reaction" }, { name: "设置值", value: "setting" }, { name: "状态", value: "status" }, { name: "流速", value: "velocity" }, ]; export const createOperationalMapSources = ({ mapUrl, workspace, }: CreateOperationalMapSourcesOptions): OperationalMapSources => { const vectorTileUrl = (name: string) => `${mapUrl}/gwc/service/tms/1.0.0/${workspace}:${name}@WebMercatorQuad@pbf/{z}/{x}/{-y}.pbf`; const vectorUrl = (name: string) => `${mapUrl}/${workspace}/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=${workspace}:${name}&outputFormat=application/json`; return { junctions: new VectorTileSource({ url: vectorTileUrl("geo_junctions"), format: new MVT(), projection: "EPSG:3857", }), pipes: new VectorTileSource({ url: vectorTileUrl("geo_pipes"), format: new MVT(), projection: "EPSG:3857", }), valves: new VectorTileSource({ url: vectorTileUrl("geo_valves"), format: new MVT(), projection: "EPSG:3857", }), reservoirs: new VectorSource({ url: vectorUrl("geo_reservoirs"), format: new GeoJSON(), }), pumps: new VectorSource({ url: vectorUrl("geo_pumps"), format: new GeoJSON(), }), tanks: new VectorSource({ url: vectorUrl("geo_tanks"), format: new GeoJSON(), }), scada: new VectorSource({ url: vectorUrl("geo_scada"), format: new GeoJSON(), }), }; }; export const createOperationalMapResources = ({ mapUrl, workspace, extent, persistent = false, sources = createOperationalMapSources({ mapUrl, workspace }), }: CreateOperationalLayersOptions) => { const layers = { junctions: new WebGLVectorTileLayer({ source: sources.junctions as any, style: defaultFlatStyle, extent, maxZoom: 24, minZoom: 11, properties: { name: "节点", value: "junctions", type: "point", properties: pointProperties, }, }), pipes: new WebGLVectorTileLayer({ source: sources.pipes as any, style: defaultFlatStyle, extent, maxZoom: 24, minZoom: 11, properties: { name: "管道", value: "pipes", type: "linestring", properties: pipeProperties, }, }), valves: new WebGLVectorTileLayer({ source: sources.valves as any, style: valveStyle, extent, maxZoom: 24, minZoom: 16, properties: { name: "阀门", value: "valves", type: "linestring", properties: [], }, }), reservoirs: new VectorLayer({ source: sources.reservoirs, style: () => createIconStyle("/icons/reservior.svg"), extent, maxZoom: 24, minZoom: 11, properties: { name: "水库", value: "reservoirs", type: "point", properties: [], }, }), pumps: new VectorLayer({ source: sources.pumps, style: pumpStyle, extent, maxZoom: 24, minZoom: 11, properties: { name: "水泵", value: "pumps", type: "linestring", properties: [], }, }), tanks: new VectorLayer({ source: sources.tanks, style: () => createIconStyle("/icons/tank.svg"), extent, maxZoom: 24, minZoom: 11, properties: { name: "水箱", value: "tanks", type: "point", properties: [], }, }), scada: new VectorLayer({ source: sources.scada, style: scadaStyle, extent, maxZoom: 24, minZoom: 11, properties: { name: "SCADA", value: "scada", type: "point", properties: [], }, }), }; if (persistent) { Object.values(layers).forEach(markMapResourcePersistent); } const layerById = layers as Record; const enabledLayers = new Set(config.MAP_AVAILABLE_LAYERS); const orderedLayers = [ "pipes", "tanks", "pumps", "reservoirs", "scada", "junctions", "valves", ] .filter((id) => enabledLayers.has(id)) .map((id) => layerById[id]); return { sources, layers, orderedLayers, resetStyles: () => { layers.junctions.setStyle(defaultFlatStyle); layers.pipes.setStyle(defaultFlatStyle); layers.valves.setStyle(valveStyle); }, }; };