import type { StyleSpecification, VectorSourceSpecification } from "maplibre-gl"; import { GEOSERVER_WORKSPACE, MAP_URL } from "./geoserver-config"; import { MAP_STYLE_TOKENS } from "./map-colors"; import { SCADA_SOURCE_ID } from "./scada"; export const WATER_NETWORK_GLOBAL_VIEW = { bbox3857: [13508801.930066336, 3608163.3499832638, 13555650.638648884, 3633685.137885742] } as const; export const SOURCE_LAYERS = { pipes: "pipes", junctions: "junctions", valves: "valves", reservoirs: "reservoirs", pumps: "pumps", tanks: "tanks", water_quality_monitoring_points: "water_quality_monitoring_points", fx_20200423_drain_valves: "fx_20200423_drain_valves", fx_20200423_service_connections: "fx_20200423_service_connections", fx_20200423_flow_meters: "fx_20200423_flow_meters", fx_20200423_hydrants: "fx_20200423_hydrants", fx_20200423_customer_meters: "fx_20200423_customer_meters", fx_20200423_pipes: "fx_20200423_pipes", fx_20200423_valves: "fx_20200423_valves", fx_20200423_non_residential_meters: "fx_20200423_non_residential_meters", fx_20200811_dn100_pipes: "fx_20200811_dn100_pipes" } as const; export const WATER_NETWORK_SOURCE_IDS = [ "pipes", "junctions", "valves", "reservoirs", "scada", "pumps", "tanks", "water_quality_monitoring_points", "fx_20200423_drain_valves", "fx_20200423_service_connections", "fx_20200423_flow_meters", "fx_20200423_hydrants", "fx_20200423_customer_meters", "fx_20200423_pipes", "fx_20200423_valves", "fx_20200423_non_residential_meters", "fx_20200811_dn100_pipes" ] as const; export type WaterNetworkSourceId = (typeof WATER_NETWORK_SOURCE_IDS)[number]; export type SupplyLayerCatalogItem = { id: WaterNetworkSourceId; sourceLayer?: (typeof SOURCE_LAYERS)[keyof typeof SOURCE_LAYERS]; geometry: "line" | "point"; available: boolean; availability: "required" | "probe"; label: string; icon: string; group: "model" | "monitoring" | "source-gis"; initialVisible: boolean; }; export const SUPPLY_LAYER_CATALOG = [ { id: "pipes", sourceLayer: SOURCE_LAYERS.pipes, geometry: "line", available: true, availability: "required", label: "管线", icon: "pipe", group: "model", initialVisible: true }, { id: "junctions", sourceLayer: SOURCE_LAYERS.junctions, geometry: "point", available: true, availability: "required", label: "节点", icon: "junction", group: "model", initialVisible: true }, { id: "valves", sourceLayer: SOURCE_LAYERS.valves, geometry: "point", available: true, availability: "probe", label: "阀门", icon: "valve", group: "model", initialVisible: true }, { id: "reservoirs", sourceLayer: SOURCE_LAYERS.reservoirs, geometry: "point", available: true, availability: "probe", label: "水库", icon: "reservoir", group: "model", initialVisible: true }, { id: "scada", sourceLayer: undefined, geometry: "point", available: true, availability: "required", label: "SCADA", icon: "scada-pressure", group: "monitoring", initialVisible: true }, { id: "pumps", sourceLayer: SOURCE_LAYERS.pumps, geometry: "point", available: true, availability: "probe", label: "水泵", icon: "pump", group: "model", initialVisible: true }, { id: "tanks", sourceLayer: SOURCE_LAYERS.tanks, geometry: "point", available: true, availability: "probe", label: "水箱", icon: "tank", group: "model", initialVisible: true }, { id: "water_quality_monitoring_points", sourceLayer: SOURCE_LAYERS.water_quality_monitoring_points, geometry: "point", available: true, availability: "probe", label: "水质监测点", icon: "water-quality", group: "monitoring", initialVisible: true }, { id: "fx_20200423_pipes", sourceLayer: SOURCE_LAYERS.fx_20200423_pipes, geometry: "line", available: true, availability: "probe", label: "GIS 管线", icon: "source-pipe", group: "source-gis", initialVisible: false }, { id: "fx_20200811_dn100_pipes", sourceLayer: SOURCE_LAYERS.fx_20200811_dn100_pipes, geometry: "line", available: true, availability: "probe", label: "DN100+ 台账", icon: "source-pipe", group: "source-gis", initialVisible: false }, { id: "fx_20200423_valves", sourceLayer: SOURCE_LAYERS.fx_20200423_valves, geometry: "point", available: true, availability: "probe", label: "GIS 阀门", icon: "source-valve", group: "source-gis", initialVisible: false }, { id: "fx_20200423_drain_valves", sourceLayer: SOURCE_LAYERS.fx_20200423_drain_valves, geometry: "point", available: true, availability: "probe", label: "排水阀", icon: "source-valve", group: "source-gis", initialVisible: false }, { id: "fx_20200423_flow_meters", sourceLayer: SOURCE_LAYERS.fx_20200423_flow_meters, geometry: "point", available: true, availability: "probe", label: "流量仪", icon: "source-meter", group: "source-gis", initialVisible: false }, { id: "fx_20200423_hydrants", sourceLayer: SOURCE_LAYERS.fx_20200423_hydrants, geometry: "point", available: true, availability: "probe", label: "消火栓", icon: "source-hydrant", group: "source-gis", initialVisible: false }, { id: "fx_20200423_service_connections", sourceLayer: SOURCE_LAYERS.fx_20200423_service_connections, geometry: "point", available: true, availability: "probe", label: "接水点", icon: "source-connection", group: "source-gis", initialVisible: false }, { id: "fx_20200423_customer_meters", sourceLayer: SOURCE_LAYERS.fx_20200423_customer_meters, geometry: "point", available: true, availability: "probe", label: "用户水表", icon: "source-meter", group: "source-gis", initialVisible: false }, { id: "fx_20200423_non_residential_meters", sourceLayer: SOURCE_LAYERS.fx_20200423_non_residential_meters, geometry: "point", available: true, availability: "probe", label: "非居民水表", icon: "source-meter", group: "source-gis", initialVisible: false } ] as const satisfies readonly SupplyLayerCatalogItem[]; export const SOURCE_GIS_SOURCE_IDS = SUPPLY_LAYER_CATALOG.filter( (layer) => layer.group === "source-gis" ).map((layer) => layer.id) as WaterNetworkSourceId[]; export function isSourceGisSourceId(value: string): value is WaterNetworkSourceId { return (SOURCE_GIS_SOURCE_IDS as readonly string[]).includes(value); } export const AVAILABLE_WATER_NETWORK_SOURCE_IDS = SUPPLY_LAYER_CATALOG.filter( (layer) => layer.available ).map((layer) => layer.id) as WaterNetworkSourceId[]; export type AvailableWaterNetworkSourceId = (typeof AVAILABLE_WATER_NETWORK_SOURCE_IDS)[number]; export type GeoServerWaterNetworkSourceId = Exclude; export const GEOSERVER_WATER_NETWORK_SOURCE_IDS = AVAILABLE_WATER_NETWORK_SOURCE_IDS.filter( (sourceId): sourceId is GeoServerWaterNetworkSourceId => sourceId !== "scada" ); export function isAvailableWaterNetworkSourceId( value: string ): value is AvailableWaterNetworkSourceId { return (AVAILABLE_WATER_NETWORK_SOURCE_IDS as readonly string[]).includes(value); } export function createWaterNetworkSources(): Record< GeoServerWaterNetworkSourceId, VectorSourceSpecification >; export function createWaterNetworkSources( sourceIds: readonly GeoServerWaterNetworkSourceId[], workspace?: string ): Partial>; export function createWaterNetworkSources( sourceIds: readonly GeoServerWaterNetworkSourceId[] = GEOSERVER_WATER_NETWORK_SOURCE_IDS, workspace = GEOSERVER_WORKSPACE ) { return Object.fromEntries( SUPPLY_LAYER_CATALOG.flatMap((layer) => layer.available && layer.sourceLayer !== undefined && sourceIds.includes(layer.id as GeoServerWaterNetworkSourceId) ? [[layer.id, createGeoServerVectorSource(layer.sourceLayer, workspace)] as const] : [] ) ) as Partial>; } function createGeoServerVectorSource( sourceLayer: (typeof SOURCE_LAYERS)[keyof typeof SOURCE_LAYERS], workspace: string ): VectorSourceSpecification { return { type: "vector", promoteId: "id", tiles: [ `${MAP_URL}/gwc/service/wmts/rest/${workspace}:${sourceLayer}/WebMercatorQuad/{z}/{y}/{x}?format=application/vnd.mapbox-vector-tile` ], minzoom: 0, maxzoom: 24 }; } function createTiandituTileUrl(layer: "vec" | "cva" | "img" | "cia", token: string) { return `https://t0.tianditu.gov.cn/${layer}_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=${layer}&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=${token}`; } export function createBaseStyle(mapboxToken?: string, tiandituToken?: string): StyleSpecification { const sources: StyleSpecification["sources"] = { [SCADA_SOURCE_ID]: { type: "geojson", promoteId: "device_id", data: { type: "FeatureCollection", features: [] } } }; const layers: StyleSpecification["layers"] = [ { id: "background", type: "background", paint: { "background-color": MAP_STYLE_TOKENS.canvas.primary } } ]; if (mapboxToken) { sources["mapbox-light"] = { type: "raster", tiles: [ `https://api.mapbox.com/styles/v1/mapbox/light-v11/tiles/512/{z}/{x}/{y}@2x?access_token=${mapboxToken}` ], tileSize: 512, attribution: '© Mapbox © OpenStreetMap' }; sources["mapbox-satellite"] = { type: "raster", tiles: [ `https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v12/tiles/512/{z}/{x}/{y}@2x?access_token=${mapboxToken}` ], tileSize: 512, attribution: '© Mapbox © OpenStreetMap' }; layers.push({ id: "mapbox-light", type: "raster", source: "mapbox-light", paint: { "raster-opacity": 0.82, "raster-saturation": -0.22, "raster-contrast": 0.04 } }); layers.push({ id: "mapbox-satellite", type: "raster", source: "mapbox-satellite", layout: { visibility: "none" }, paint: { "raster-opacity": 0.86, "raster-saturation": -0.3, "raster-contrast": 0.1 } }); } if (tiandituToken) { const tiandituSources = [ ["tianditu-vector", "vec"], ["tianditu-vector-annotation", "cva"], ["tianditu-image", "img"], ["tianditu-image-annotation", "cia"] ] as const; tiandituSources.forEach(([sourceId, layer]) => { sources[sourceId] = { type: "raster", tiles: [createTiandituTileUrl(layer, tiandituToken)], tileSize: 256, maxzoom: 18, attribution: '© 天地图' }; }); const vectorVisible = mapboxToken ? "none" : "visible"; layers.push( { id: "tianditu-vector", type: "raster", source: "tianditu-vector", layout: { visibility: vectorVisible }, paint: { "raster-opacity": 0.9 } }, { id: "tianditu-vector-annotation", type: "raster", source: "tianditu-vector-annotation", layout: { visibility: vectorVisible } }, { id: "tianditu-image", type: "raster", source: "tianditu-image", layout: { visibility: "none" }, paint: { "raster-opacity": 0.9, "raster-saturation": -0.12, "raster-contrast": 0.06 } }, { id: "tianditu-image-annotation", type: "raster", source: "tianditu-image-annotation", layout: { visibility: "none" } } ); } return { version: 8, glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", sources, layers }; }