import type { StyleSpecification, VectorSourceSpecification } from "maplibre-gl"; import { GEOSERVER_WORKSPACE, MAP_URL } from "./geoserver-config"; import { MAP_STYLE_TOKENS } from "./map-colors"; import { SCADA_ANALYSIS_SOURCE_ID, createEmptyScadaAnalysisCollection } from "./scada-analysis"; import { SCADA_SOURCE_ID } from "./scada"; export const WATER_NETWORK_GLOBAL_VIEW = { bbox3857: [13508801.930066336, 3608163.3499832638, 13555650.638648884, 3633685.137885742], bbox4326: [121.35162408429075, 30.81049932304578, 121.77248479490075, 31.007207809222017] } as const; export const SOURCE_LAYERS = { pipes: "pipes", junctions: "junctions", valves: "valves", reservoirs: "reservoirs", pumps: "pumps", tanks: "tanks" } as const; export const WATER_NETWORK_SOURCE_IDS = [ "pipes", "junctions", "valves", "reservoirs", "scada", "pumps", "tanks" ] 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; }; export const SUPPLY_LAYER_CATALOG = [ { id: "pipes", sourceLayer: SOURCE_LAYERS.pipes, geometry: "line", available: true, availability: "required", label: "管线", icon: "pipe" }, { id: "junctions", sourceLayer: SOURCE_LAYERS.junctions, geometry: "point", available: true, availability: "required", label: "节点", icon: "junction" }, { id: "valves", sourceLayer: SOURCE_LAYERS.valves, geometry: "point", available: true, availability: "probe", label: "阀门", icon: "valve" }, { id: "reservoirs", sourceLayer: SOURCE_LAYERS.reservoirs, geometry: "point", available: true, availability: "probe", label: "水库", icon: "reservoir" }, { id: "scada", sourceLayer: undefined, geometry: "point", available: true, availability: "required", label: "SCADA", icon: "scada-pressure" }, { id: "pumps", sourceLayer: SOURCE_LAYERS.pumps, geometry: "point", available: true, availability: "probe", label: "水泵", icon: "pump" }, { id: "tanks", sourceLayer: SOURCE_LAYERS.tanks, geometry: "point", available: true, availability: "probe", label: "水箱", icon: "tank" } ] as const satisfies readonly SupplyLayerCatalogItem[]; 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; export function createWaterNetworkSources( sourceIds: readonly GeoServerWaterNetworkSourceId[] ): Partial>; export function createWaterNetworkSources( sourceIds: readonly GeoServerWaterNetworkSourceId[] = GEOSERVER_WATER_NETWORK_SOURCE_IDS ) { return Object.fromEntries( SUPPLY_LAYER_CATALOG.flatMap((layer) => layer.available && layer.sourceLayer !== undefined && sourceIds.includes(layer.id as GeoServerWaterNetworkSourceId) ? [[layer.id, createGeoServerVectorSource(layer.sourceLayer)] as const] : [] ) ) as Partial>; } function createGeoServerVectorSource( sourceLayer: (typeof SOURCE_LAYERS)[keyof typeof SOURCE_LAYERS] ): VectorSourceSpecification { return { type: "vector", promoteId: "id", tiles: [ `${MAP_URL}/gwc/service/wmts/rest/${GEOSERVER_WORKSPACE}:${sourceLayer}/WebMercatorQuad/{z}/{y}/{x}?format=application/vnd.mapbox-vector-tile` ], minzoom: 0, maxzoom: 24, bounds: [...WATER_NETWORK_GLOBAL_VIEW.bbox4326] }; } export function createBaseStyle(mapboxToken?: string): StyleSpecification { const sources: StyleSpecification["sources"] = { [SCADA_SOURCE_ID]: { type: "geojson", promoteId: "device_id", data: { type: "FeatureCollection", features: [] } }, [SCADA_ANALYSIS_SOURCE_ID]: { type: "geojson", data: createEmptyScadaAnalysisCollection() } }; 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 } }); } return { version: 8, glyphs: "https://demotiles.maplibre.org/font/{fontstack}/{range}.pbf", sources, layers }; }