bxztApp/packages/screen/src/map/services/createLayerService.js

424 lines
16 KiB
JavaScript
Raw Normal View History

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,
2025-11-19 13:57:24 +08:00
cql_filter:queryParams.get('cql_filter')||'',
2025-11-19 13:41:02 +08:00
...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)
},
}
}