DC-SDK 实战指南:基于 Cesium 的三维数字孪生大屏开发
前言
在当今数字孪生、智慧城市等领域的开发中,三维地图可视化已经成为核心需求。本文将结合一个实际的政务大屏项目,分享如何使用 DC-SDK(@dvgis/dc-sdk)快速构建高性能的三维可视化应用。
本文基于实际生产项目
databoard-ui,涉及人口、房屋、场所、事件等多维度数据的三维可视化展示。
一、DC-SDK 是什么
DC-SDK 是一款基于 Cesium 的 WebGL 三维地图可视化开发框架,它在 Cesium 原生 API 的基础上进行了封装和增强,提供了:
- 更简洁的 API 设计
- 丰富的图层类型(矢量图层、聚合图层、GeoJSON图层、3DTiles图层等)
- 完善的事件交互体系
- Vue/React 友好的集成方式
安装方式
bash
npm install @dvgis/dc-sdk
javascript
// main.js 中引入
import * as DC from '@dvgis/dc-sdk'
import '@dvgis/dc-sdk/dist/dc.min.css'
// 挂载到全局,方便使用
window.DC = DC
二、初始化三维地球
基础配置
在 Vue 项目中,我们通常将地图实例管理放在 Vuex 中:
javascript
// store/modules/map.js
initMapInstance({ commit, state }) {
return new Promise((resolve, reject) => {
DC.ready().then(async () => {
const viewer = new DC.Viewer('viewer-container', {
sceneMode: DC.SceneMode.SCENE3D, // 3D模式
baseLayer: true, // 启用基础图层
enableCursorStyle: false, // 自定义鼠标样式
scene3DOnly: true, // 仅3D场景
requestRenderMode: true, // 请求渲染模式(性能优化)
})
// 配置地球外观和相机控制
viewer.setOptions({
showAtmosphere: false, // 隐藏大气层
showSun: false, // 隐藏太阳
showMoon: false, // 隐藏月亮
cameraController: {
minimumZoomDistance: 280, // 最小缩放距离
maximumZoomDistance: 500000, // 最大缩放距离
},
globe: {
show: true,
showGroundAtmosphere: false, // 隐藏地面大气层
enableLighting: false, // 禁用光照
baseColor: DC.Color.fromCssColorString("rgba(0, 23, 75, 1)"),
},
skyBox: { show: false } // 隐藏天空盒
})
commit('SET_MAP_INSTANCE', viewer)
resolve(viewer)
})
})
}
添加影像底图
javascript
// 创建 XYZ 瓦片底图
const baseLayer = DC.ImageryLayerFactory.createXYZImageryLayer({
url: process.env.VUE_APP_IMAGERYLAYER_URL_XYZ,
style: "elec",
tilingScheme: new DC.WebMercatorTilingScheme(),
crs: "WGS84",
rectangle: {
west: -180 * Math.PI / 180,
south: -85.05 * Math.PI / 180,
east: 180 * Math.PI / 180,
north: 85.05 * Math.PI / 180
}
})
await viewer.addBaseLayer([baseLayer], { brightness: 0.1 })
三、核心图层实战
1. 聚合图层 (ClusterLayer) - 海量点位展示
聚合图层是处理海量点位数据的利器,自动根据缩放级别聚合显示:
javascript
// 创建聚合图层
this.layers.clusterLayer = new DC.ClusterLayer('clusterLayer', {
radius: 40, // 聚合像素范围
maxZoom: 25, // 最大聚合缩放级别
image: require('@/assets/img/icon-cs.png'), // 单点图标
gradientColors: { // 聚合颜色渐变
"0.0001": DC.Color.BLUEVIOLET,
"0.001": DC.Color.BLUEVIOLET,
"0.01": DC.Color.BLUEVIOLET,
"0.1": DC.Color.BLUEVIOLET
},
fontColor: DC.Color.WHITE, // 数字颜色
style: 'circle', // 样式:circle/clustering/custom
clusterSize: 16, // 聚合图标尺寸
})
// 设置点位数据
const positions = dataList.map(item => ({
attr: item, // 自定义属性,点击时可获取
lng: item.lng,
lat: item.lat
}))
this.layers.clusterLayer.setPoints(positions)
this.viewer.addLayer(this.layers.clusterLayer)
聚合点点击事件处理:
javascript
this.layers.clusterLayer.on(DC.MouseEventType.CLICK, (movement) => {
const { attr } = movement.overlay
// 判断是聚合点还是单个点
if (Object.keys(attr).includes('count')) {
// 聚合点:获取聚合内的点列表
const leaves = this.layers.clusterLayer.getLeaves(attr.properties.cluster_id)
// 或者放大到展开级别
const expansionZoom = this.layers.clusterLayer.getClusterExpansionZoom(attr.properties.cluster_id)
this.viewer.camera.flyTo({ destination: ... })
} else {
// 单个点:显示详情信息
this.showDetailPopup(attr)
}
})
2. GeoJSON 图层 - 区域边界可视化
加载 GeoJSON 数据展示行政区划:
javascript
async renderGeoJson(geoJson) {
// 创建 GeoJsonLayer
this.layers.geoJsonLayer = new DC.GeoJsonLayer('geoJsonLayer', geoJson, {
clampToGround: true, // 贴地
pickable: true, // 可点击(重要!)
fill: DC.Color.fromCssColorString("rgba(0, 0, 0, 0)"),
stroke: DC.Color.fromCssColorString("rgba(0, 171, 255, 1)"),
strokeWidth: 10,
})
this.viewer.addLayer(this.layers.geoJsonLayer)
// 遍历每个多边形,添加拉伸效果
const delegate = await this.layers.geoJsonLayer.delegate
this.layers.geoJsonLayer.eachOverlay((item) => {
if (item.polygon) {
let polygon = DC.Polygon.fromEntity(item)
polygon.setStyle({
height: 198,
material: DC.Color.fromCssColorString("rgba(0, 171, 255, 0.2)"),
extrudedHeight: 200, // 拉伸高度,形成3D效果
})
this.vectorLayer.addOverlay(polygon)
}
})
}
3. 3DTiles 图层 - 城市建筑白模
加载城市级 3DTiles 数据,实现建筑白模渲染:
javascript
loadWhiteTileset() {
const tileset = new DC.Tileset(tilesetUrl, {
// ⭐ 核心性能优化参数
maximumScreenSpaceError: 16, // 降低值更流畅
maximumNumberOfLoadedTiles: 1000, // 限制同时加载瓦片数
skipLevelOfDetail: true, // 跳过中间LOD
maximumMemoryUsage: 512, // 内存限制(MB)
// 视锥体优化
cullRequestsWhileMoving: true, // 移动时暂停新请求
cullRequestsWhileMovingMultiplier: 60,
// 动态调整精度
dynamicScreenSpaceError: true,
dynamicScreenSpaceErrorDensity: 0.00278,
dynamicScreenSpaceErrorFactor: 4.0,
})
// 自定义着色器,统一建筑颜色
tileset.ready((tileset) => {
tileset.customShader = new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
material.diffuse = vec3(0.0/255.0, 191.0/255.0, 255.0/255.0);
}
`
})
})
this.tilesetLayer.addOverlay(tileset)
this.viewer.addLayer(this.tilesetLayer)
}
4. HTML 图层 - 自定义弹窗
使用 HtmlLayer 实现 Vue 组件弹窗:
javascript
// 创建 HTML 图层
this.layers.htmlLayer = new DC.HtmlLayer('htmlLayer')
this.viewer.addLayer(this.layers.htmlLayer)
// 在指定位置创建 HTML 图标
createHtmlIcon(options) {
const { position, htmlLayer, props, component } = options
const id_html = "id_" + Date.now()
// 创建 DivIcon
let htmlIcon = new DC.DivIcon(
new DC.Position(position.lng, position.lat),
`<div id="${id_html}"></div>`
)
htmlIcon.setStyle({
className: 'custom-popup',
zIndex: '2200'
})
htmlLayer.addOverlay(htmlIcon)
// 将 Vue 组件挂载到 DOM
const vm = mountRemote(id_html, props, component)
return vm
}
// 使用示例
const vm = this.createHtmlIcon({
position: { lng: 118.18, lat: 28.42 },
htmlLayer: this.layers.htmlLayer,
props: { title: '详情信息' },
component: PlaceDetails, // Vue 组件
})
vm.$on('close', () => {
this.layers.htmlLayer.clear()
})
四、相机控制与视角管理
常用相机操作
javascript
// 1. 设置初始视角
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(118.18, 28.42, 1500),
orientation: {
heading: Cesium.Math.toRadians(0), // 正北
pitch: Cesium.Math.toRadians(-45), // 俯角45度
roll: 0
}
})
// 2. 飞行到指定位置(带动画)
viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(lng, lat, height),
orientation: { heading: 0, pitch: -45, roll: 0 },
duration: 2, // 飞行时间
complete: () => console.log('到达目标')
})
// 3. 飞行到边界范围(适配数据)
viewer.flyToBounds([minLon, minLat, maxLon, maxLat], {
heading: 0,
pitch: -90,
roll: 0
})
// 4. 计算边界球飞行(适配大量点位)
const positions = points.map(p =>
Cesium.Cartesian3.fromDegrees(p.lng, p.lat)
)
const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
viewer.camera.flyToBoundingSphere(boundingSphere, {
duration: 2,
padding: new Cesium.HeadingPitchRange(0, -0.5, boundingSphere.radius * 1.5)
})
监听相机高度变化
javascript
viewer.camera.changed.addEventListener(() => {
const zoom = viewer.zoom
const currentHeight = viewer.camera.positionCartographic.height
console.log('当前相机高度: ' + currentHeight.toFixed(2) + ' 米')
})
五、性能优化技巧
1. 渲染模式优化
javascript
const viewer = new DC.Viewer('viewer-container', {
requestRenderMode: true, // 请求渲染模式,只在需要时渲染
maximumRenderTimeChange: 0.5, // 最大渲染间隔
})
2. 3DTiles 性能调优
javascript
const tileset = new DC.Tileset(url, {
// 随相机移动动态调整精度
maximumScreenSpaceError: 16,
// 移动时降低精度
cullRequestsWhileMoving: true,
cullRequestsWhileMovingMultiplier: 60.0,
// 相机停止后恢复精度
})
viewer.camera.moveStart.addEventListener(() => {
tileset.maximumScreenSpaceError = 32
})
viewer.camera.moveEnd.addEventListener(() => {
setTimeout(() => {
tileset.maximumScreenSpaceError = 8
}, 500)
})
3. 图层生命周期管理
javascript
// 页面离开时清理图层
beforeDestroy() {
for (let key in this.layers) {
this.viewer.removeLayer(this.layers[key])
}
}
六、完整业务封装示例
将地图操作封装为业务类,便于各页面复用:
javascript
// layout/dcMap/common.js
export default class Common {
layers = {}
viewer = null
vueInstance = null
mapUtils = null
constructor(viewer, vueInstance) {
this.viewer = viewer
this.vueInstance = vueInstance
this.mapUtils = new MapUtils(this.viewer)
}
initCommon() {
// 原型方法扩展
for (const key in viewerRewrite) {
DC.Viewer.prototype[key] = viewerRewrite[key]
}
}
// 工具方法
zoomIn() {
this.mapUtils._zoomIn()
}
zoomOut() {
this.mapUtils._zoomOut()
}
homeLayer() {
const homePosition = store.state.map.currentView.homePosition
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
homePosition.longitude,
homePosition.latitude,
homePosition.height
),
orientation: { ... }
})
}
}
业务类继承:
javascript
// layout/dcMap/dashboard.js
import Common from './common.js'
export default class Dashboard extends Common {
constructor(viewer, vueInstance) {
super(viewer, vueInstance)
this.initMap()
}
initMap() {
this.initCommon()
// 初始化业务图层
this.layers.vectorLayer = new DC.VectorLayer('vectorLayer')
this.viewer.addLayer(this.layers.vectorLayer)
}
// 业务方法...
renderPopulation(data) { }
renderHouse(data) { }
}
七、项目结构参考
csharp
src/
├── layout/
│ ├── DcMap.vue # 地图容器组件
│ └── dcMap/
│ ├── common.js # 基础地图操作类
│ ├── dashboard.js # 首页业务操作类
│ ├── place.js # 场所业务操作类
│ └── event.js # 事件业务操作类
├── store/
│ └── modules/
│ └── map.js # 地图状态管理
├── utils/
│ └── mapUtils.js # 地图工具函数
└── views/
└── Dashboard.vue # 首页大屏
八、常见问题与解决方案
Q1: 弹窗被地球遮挡?
javascript
// 设置 disableDepthTestDistance 禁用深度测试
billboard.disableDepthTestDistance = Number.POSITIVE_INFINITY
Q2: 图层点击事件不响应?
javascript
// 创建图层时必须开启 pickable
const layer = new DC.GeoJsonLayer(id, geoJson, {
pickable: true // 必须开启!
})
Q3: 3DTiles 加载太慢?
javascript
// 调整 maximumScreenSpaceError
const tileset = new DC.Tileset(url, {
maximumScreenSpaceError: 32, // 值越大越模糊但越快
skipLevelOfDetail: true
})
Q4: 内存持续增长?
javascript
// 及时清理图层和实体
this.viewer.entities.removeAll()
this.viewer.dataSources.removeAll()
this.viewer.scene.primitives.removeAll()
总结
DC-SDK 为 Cesium 开发提供了更友好的开发体验,通过本文介绍的:
- 基础初始化 - 地球创建与底图配置
- 核心图层 - ClusterLayer、GeoJsonLayer、Tileset、HtmlLayer
- 相机控制 - 视角管理与飞行控制
- 性能优化 - 渲染模式与资源管理
- 工程封装 - 面向业务的类封装
希望这篇实战指南能帮助你快速上手 DC-SDK,构建出高性能的三维数字孪生应用。
参考资源: