bxztApp/packages/screen/src/map/services/createLayerService.js
2025-11-19 13:57:24 +08:00

424 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
},
}
}