import * as Cesium from 'cesium' import { SplitDirection } from 'cesium' // 依赖:{ viewerOrThrow, store } export function createLayerService(deps) { const { viewerOrThrow, store } = deps // 影像图层 zIndex 辅助函数 function nextZIndex() { const zIndexValues = Object.values(store.layers) .filter((record) => record && record.type === 'imagery') .map((record) => (record.meta && typeof record.meta.zIndex === 'number' ? record.meta.zIndex : 0)) return (zIndexValues.length ? Math.max(...zIndexValues) : 0) + 1 } // 按 zIndex 重新整理影像图层的叠放顺序 function adjustImageryOrder(viewer) { try { const imageryRecords = Object.values(store.layers) .filter((record) => record && record.type === 'imagery' && record.obj) .sort((a, b) => { const aZ = a.meta && typeof a.meta.zIndex === 'number' ? a.meta.zIndex : 0 const bZ = b.meta && typeof b.meta.zIndex === 'number' ? b.meta.zIndex : 0 return aZ - bZ }) // raiseToTop in ascending order so the highest ends top-most imageryRecords.forEach((record) => { try { if (viewer.imageryLayers.contains(record.obj)) viewer.imageryLayers.raiseToTop(record.obj) } catch (e) {} }) syncImageryOrderMeta(viewer) } catch (e) {} } /** * @description 同步影像图层元数据里的排序索引,保持与 Cesium 实际顺序一致 */ function syncImageryOrderMeta(viewer) { try { const imageryLayers = viewer.imageryLayers const imageryRecords = Object.values(store.layers) .filter((record) => record && record.type === 'imagery' && record.obj) const lookup = new Map() imageryRecords.forEach((record) => lookup.set(record.obj, record)) const count = imageryLayers.length for (let i = 0; i < count; i += 1) { const layer = imageryLayers.get(i) const record = lookup.get(layer) if (!record) continue if (!record.meta) record.meta = {} record.meta.zIndex = i } } catch (e) {} } /** * @description 同步矢量数据源图层顺序,记录在 meta.vectorOrder 中 */ function syncVectorOrderMeta(viewer) { try { const dataSources = viewer.dataSources const vectorRecords = Object.values(store.layers) .filter((record) => record && (record.type === 'vector' || record.type === 'datasource') && record.obj) const lookup = new Map() vectorRecords.forEach((record) => lookup.set(record.obj, record)) const count = dataSources.length for (let i = 0; i < count; i += 1) { const ds = dataSources.get(i) const record = lookup.get(ds) if (!record) continue if (!record.meta) record.meta = {} record.meta.vectorOrder = i } } catch (e) {} } return { async addLayer(spec) { const viewer = viewerOrThrow() // 兼容新旧两种参数风格(新的 serviceConfig 与旧的直传 spec) const layerSpec = spec const layerType = layerSpec.type const layerId = layerSpec.id || (layerType ? `${layerType}:${Date.now().toString(36)}` : `layer:${Date.now().toString(36)}`) if (store.layers[layerId]) return layerId const metadata = { ...(layerSpec.meta || {}) } if (layerSpec.zIndex != null) metadata.zIndex = Number(layerSpec.zIndex) if (metadata.zIndex == null && (layerType && layerType !== 'terrain' && layerType !== 'primitive' && layerType !== 'vector' && layerType !== 'datasource')) { metadata.zIndex = nextZIndex() } const layerOptions = layerSpec.options || {} const sourceUrl = layerSpec.url let layerRecord = null // 注册影像图层(ImageryLayer)的辅助方法 const registerImageryLayer = (provider, extraProps = {}) => { const imageryLayer = viewer.imageryLayers.addImageryProvider(provider) if (typeof layerOptions.opacity === 'number') imageryLayer.alpha = layerOptions.opacity if (typeof layerOptions.visible === 'boolean') imageryLayer.show = layerOptions.visible imageryLayer.splitDirection = SplitDirection.NONE const record = { id: layerId, type: 'imagery', obj: imageryLayer, owned: true, show: imageryLayer.show, opacity: imageryLayer.alpha, meta: metadata, ...extraProps, } store.layers[layerId] = record adjustImageryOrder(viewer) return record } // 注册矢量数据源的辅助方法 const registerVectorLayer = async (dataSource) => { await viewer.dataSources.add(dataSource) dataSource.show = layerOptions.visible !== false const record = { id: layerId, type: 'vector', obj: dataSource, owned: true, show: dataSource.show, opacity: typeof layerOptions.opacity === 'number' ? layerOptions.opacity : 1, meta: metadata, } store.layers[layerId] = record syncVectorOrderMeta(viewer) return record } // 旧版直映射类型分支 if (layerType === 'imagery' || layerType === 'baseImagery') { const provider = layerSpec.source layerRecord = registerImageryLayer(provider) if (layerType === 'baseImagery') viewer.imageryLayers.lowerToBottom(layerRecord.obj) return layerId } if (layerType === 'vector' || layerType === 'datasource') { const dataSource = new Cesium.CustomDataSource(layerId) layerRecord = await registerVectorLayer(dataSource) return layerId } if (layerType === 'terrain') { if ('terrain' in viewer) viewer.terrain = layerSpec.source else viewer.scene.terrainProvider = layerSpec.source layerRecord = { id: layerId, type: 'terrain', obj: layerSpec.source, owned: true, show: true, opacity: 1, meta: metadata, } store.layers[layerId] = layerRecord return layerId } if (layerType === 'primitive') { const primitive = layerSpec.source viewer.scene.primitives.add(primitive) layerRecord = { id: layerId, type: 'primitive', obj: primitive, owned: true, show: true, opacity: 1, meta: metadata, } store.layers[layerId] = layerRecord return layerId } // React serviceConfig-style types switch (layerType) { case 'ArcGISTiledMapServiceLayer': { const haveTemplateXYZ = typeof sourceUrl === 'string' && sourceUrl.includes('{z}/{y}/{x}') if (!haveTemplateXYZ) { const provider = await Cesium.ArcGisMapServerImageryProvider.fromUrl(sourceUrl, layerOptions) registerImageryLayer(provider) } else { const provider = new Cesium.UrlTemplateImageryProvider({ url: sourceUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: 18, ...layerOptions, }) registerImageryLayer(provider) } break } case 'ArcGISDynamicMapServiceLayer': case 'ArcGISImageMapServiceLayer': { const provider = await Cesium.ArcGisMapServerImageryProvider.fromUrl(sourceUrl, { enablePickFeatures: true, ...layerOptions, }) registerImageryLayer(provider) break } case 'GeoJSONServiceLayer': { // Accept url or raw data in options.data const data = layerOptions.data || sourceUrl const dataSource = await Cesium.GeoJsonDataSource.load(data, layerOptions) await registerVectorLayer(dataSource) break } case 'WmsServiceLayer': { const base = (sourceUrl || '').split('?')[0] const queryParams = (sourceUrl || '').includes('?') ? new URLSearchParams((sourceUrl || '').split('?')[1]) : new URLSearchParams() const provider = new Cesium.WebMapServiceImageryProvider({ url: base, layers: queryParams.get('layers') || layerOptions.layers, parameters: { service: 'WMS', version: queryParams.get('version') || layerOptions.version || '1.1.1', request: 'GetMap', format: queryParams.get('format') || layerOptions.format || 'image/png', transparent: true, cql_filter:queryParams.get('cql_filter')||'', ...layerOptions.extraParameters, ...layerOptions.parameters, }, enablePickFeatures: true, ...layerOptions, }) registerImageryLayer(provider) break } case 'WmtsServiceLayer': case 'TiandituVecLayer': case 'TiandituImgLayer': case 'TiandituCvaLayer': { // Try to honor tk from url or options const urlBase = (sourceUrl || '').split('?')[0] const queryParams = (sourceUrl || '').includes('?') ? new URLSearchParams((sourceUrl || '').split('?')[1]) : new URLSearchParams() const tk = queryParams.get('tk') || layerOptions.tk const wmtsUrl = tk ? `${urlBase}?tk=${tk}` : urlBase const provider = new Cesium.WebMapTileServiceImageryProvider({ url: wmtsUrl, layer: queryParams.get('LAYER') || queryParams.get('layer') || layerOptions.layer || 'img', style: 'default', format: 'tiles', tileMatrixSetID: layerOptions.tileMatrixSetID || 'w', tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: layerOptions.maximumLevel || 18, subdomains: layerOptions.subdomains || ['0', '1', '2', '3', '4', '5', '6', '7'], ...layerOptions, }) registerImageryLayer(provider) break } case 'WebTileLayer': { const provider = new Cesium.UrlTemplateImageryProvider({ url: sourceUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), subdomains: layerOptions.subdomains || ['0', '1', '2', '3', '4', '5', '6', '7'], ...layerOptions, }) registerImageryLayer(provider) break } case 'TMSServiceLayer': { // TMS z/x/{reverseY} const templateUrl = typeof sourceUrl === 'string' ? sourceUrl.replace('{y}', '{reverseY}') : sourceUrl const providerOptions = { url: templateUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: layerOptions.maximumLevel || layerOptions.maxZoom || 22, ...layerOptions, } if (layerOptions.bounds) { const bounds = layerOptions.bounds providerOptions.rectangle = Cesium.Rectangle.fromDegrees(bounds.west, bounds.south, bounds.east, bounds.north) } const provider = new Cesium.UrlTemplateImageryProvider({ ...providerOptions, url: templateUrl }) registerImageryLayer(provider) break } case 'TmsServiceLayer': { // XYZ z/x/y const providerOptions = { url: sourceUrl, tilingScheme: new Cesium.WebMercatorTilingScheme(), maximumLevel: layerOptions.maximumLevel || layerOptions.maxZoom || 22, ...layerOptions, } if (layerOptions.bounds) { const bounds = layerOptions.bounds providerOptions.rectangle = Cesium.Rectangle.fromDegrees(bounds.west, bounds.south, bounds.east, bounds.north) } const provider = new Cesium.UrlTemplateImageryProvider(providerOptions) registerImageryLayer(provider) break } case 'Cesium3DTileService': { const tileset = await Cesium.Cesium3DTileset.fromUrl(sourceUrl, { ...layerOptions, }) viewer.scene.primitives.add(tileset) layerRecord = { id: layerId, type: 'primitive', obj: tileset, owned: true, show: true, opacity: 1, meta: metadata, } store.layers[layerId] = layerRecord break } default: throw new Error('不支持的图层类型: ' + layerType) } return layerId }, // 移除图层 removeLayer(id) { const viewer = viewerOrThrow() const record = store.layers[id] if (!record) return false try { if (record.type === 'imagery') { viewer.imageryLayers.remove(record.obj, true) syncImageryOrderMeta(viewer) } else if (record.type === 'vector' || record.type === 'datasource') { viewer.dataSources.remove(record.obj, true) syncVectorOrderMeta(viewer) } else if (record.type === 'primitive') { viewer.scene.primitives.remove(record.obj) } else if (record.type === 'terrain') { if ('terrain' in viewer) viewer.terrain = new Cesium.EllipsoidTerrain() else viewer.scene.terrainProvider = new Cesium.EllipsoidTerrain() } } catch (e) {} delete store.layers[id] return true }, // 显隐图层 showLayer(id, visible) { const record = store.layers[id] if (!record) return if (record.type === 'imagery') record.obj.show = !!visible else if (record.type === 'vector' || record.type === 'datasource') record.obj.show = !!visible else if (record.type === 'primitive') record.obj.show = !!visible record.show = !!visible }, // 设置透明度 setOpacity(id, alpha) { const record = store.layers[id] if (!record) return if (record.type === 'imagery') { record.obj.alpha = alpha record.opacity = alpha } else { record.opacity = alpha /* TODO: walk entities/materials */ } }, // 调整图层顺序(上/下/置顶/置底) moveLayer(id, direction) { const viewer = viewerOrThrow() const record = store.layers[id] if (!record) return if (record.type === 'imagery') { const imageryLayers = viewer.imageryLayers if (direction === 'up') imageryLayers.raise(record.obj) else if (direction === 'down') imageryLayers.lower(record.obj) else if (direction === 'top') imageryLayers.raiseToTop(record.obj) else if (direction === 'bottom') imageryLayers.lowerToBottom(record.obj) syncImageryOrderMeta(viewer) } else if (record.type === 'vector' || record.type === 'datasource') { const dataSources = viewer.dataSources if (direction === 'up') dataSources.raise(record.obj) else if (direction === 'down') dataSources.lower(record.obj) else if (direction === 'top') dataSources.raiseToTop(record.obj) else if (direction === 'bottom') dataSources.lowerToBottom(record.obj) syncVectorOrderMeta(viewer) } }, // 设置卷帘(左右分屏)位置 setSplit(id, side) { const record = store.layers[id] if (!record || record.type !== 'imagery') return const splitDirectionMap = { left: SplitDirection.LEFT, right: SplitDirection.RIGHT, none: SplitDirection.NONE, } record.obj.splitDirection = splitDirectionMap[side] || SplitDirection.NONE }, // 设置全局卷帘分割位置 [0,1] setSplitPosition(position) { const viewer = viewerOrThrow() store.imagerySplitPosition = Math.min(1, Math.max(0, position)) try { viewer.scene.imagerySplitPosition = store.imagerySplitPosition } catch (e) {} }, // 获取图层记录 getLayer(id) { return store.layers[id] }, // 列出所有图层记录 listLayers() { return Object.values(store.layers) }, } }