在开始处理地理数据之前,我们需要先搭建好开发环境。我推荐使用Vite作为构建工具,它能提供极快的热更新速度,特别适合Cesium这种需要频繁调试3D场景的项目。
首先创建一个新的Vue3项目:
bash复制npm create vite@latest vue3-cesium-demo --template vue
cd vue3-cesium-demo
npm install cesium @cesium/engine @cesium/widgets --save
安装完成后,我们需要配置Cesium的静态资源。在vite.config.js中添加以下配置:
javascript复制import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'cesium': path.resolve(__dirname, 'node_modules/cesium')
}
},
server: {
port: 3000
}
})
接下来在main.js中初始化Cesium:
javascript复制import { createApp } from 'vue'
import App from './App.vue'
import { Ion } from 'cesium'
// 重要:设置Cesium静态资源路径
window.CESIUM_BASE_URL = '/node_modules/cesium/Build/Cesium/'
Ion.defaultAccessToken = '你的Cesium Ion访问令牌'
createApp(App).mount('#app')
提示:Cesium Ion是一个托管地理数据的平台,注册账号后可以获取免费配额。如果只是本地开发测试,不设置token也可以运行基础功能。
GeoJSON是WebGIS开发中最常用的数据格式之一,它基于JSON标准,可以直接被JavaScript解析。我在实际项目中最常处理的是行政区划数据,比如省市县边界。
获取GeoJSON数据有几个可靠来源:
这里我推荐一个快速获取中国地图GeoJSON的方法:
javascript复制const chinaGeoJsonUrl = 'https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json'
加载GeoJSON的核心方法是GeoJsonDataSource.load(),这个方法返回一个Promise。下面是一个完整的加载示例:
javascript复制import { onMounted } from 'vue'
import * as Cesium from 'cesium'
export default {
setup() {
onMounted(() => {
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain()
})
// 加载GeoJSON并设置样式
const promise = Cesium.GeoJsonDataSource.load(chinaGeoJsonUrl, {
stroke: Cesium.Color.RED,
fill: Cesium.Color.SKYBLUE.withAlpha(0.3),
strokeWidth: 2,
clampToGround: true
})
promise.then(dataSource => {
viewer.dataSources.add(dataSource)
// 获取所有实体并设置随机高度
const entities = dataSource.entities.values
entities.forEach(entity => {
entity.polygon.extrudedHeight = Math.random() * 100000
entity.polygon.material = Cesium.Color.fromRandom({
alpha: 0.7
})
})
// 缩放到中国区域
viewer.camera.flyTo({
destination: Cesium.Rectangle.fromDegrees(73, 18, 135, 53)
})
})
})
}
}
当地图数据量较大时,我总结了几点优化经验:
clampToGround:true让数据贴合地形javascript复制// 性能优化后的加载方式
const worker = new Worker('./geoJsonWorker.js')
worker.postMessage({ url: chinaGeoJsonUrl })
worker.onmessage = (e) => {
const simplifiedData = e.data
Cesium.GeoJsonDataSource.load(simplifiedData, {
// 优化参数
clampToGround: true,
fill: Cesium.Color.TRANSPARENT,
strokeWidth: 1
}).then(/* ... */)
}
KML(Keyhole Markup Language)是Google Earth使用的标记语言,相比GeoJSON,它能存储更丰富的信息,包括:
一个典型的KML结构如下:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document>
<name>示例KML</name>
<Placemark>
<name>北京</name>
<Point>
<coordinates>116.404,39.915,0</coordinates>
</Point>
</Placemark>
</Document>
</kml>
Cesium通过KmlDataSource加载KML/KMZ文件,处理方式与GeoJSON类似但有几点不同:
javascript复制const kmlUrl = './data/sample.kml'
const viewer = new Cesium.Viewer('cesiumContainer')
viewer.dataSources.add(
Cesium.KmlDataSource.load(kmlUrl, {
camera: viewer.scene.camera, // 用于网络链接刷新
canvas: viewer.scene.canvas, // 用于屏幕叠加层
clampToGround: true // 使线要素贴合地形
})
)
对于KMZ文件(压缩的KML),加载方式完全相同:
javascript复制const kmzUrl = './data/sample.kmz'
Cesium.KmlDataSource.load(kmzUrl).then(/* ... */)
KML支持网络链接(NetworkLink),可以动态更新数据。这在实时监控场景中非常有用:
javascript复制const options = {
camera: viewer.scene.camera,
canvas: viewer.scene.canvas,
sourceUri: 'http://your-server.com/kml' // 基础URL
}
// 定时刷新
setInterval(() => {
viewer.dataSources.removeAll()
Cesium.KmlDataSource.load('realtime.kml', options)
.then(dataSource => viewer.dataSources.add(dataSource))
}, 5000)
让用户可以点击要素查看详细信息是基本需求:
javascript复制const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
handler.setInputAction((movement) => {
const picked = viewer.scene.pick(movement.position)
if (picked && picked.id) {
const entity = picked.id
// 显示属性信息
console.log(entity.properties)
// 或显示自定义信息框
viewer.selectedEntity = entity
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
根据属性筛选显示特定要素:
javascript复制function filterByProperty(propertyName, value) {
dataSource.entities.values.forEach(entity => {
const show = entity.properties[propertyName]?.getValue() === value
entity.show = show
})
}
// 示例:只显示GDP大于5000的区域
filterByProperty('GDP', (val) => val > 5000)
使用Cesium的动画系统让数据更生动:
javascript复制entities.forEach((entity, i) => {
entity.polygon.extrudedHeight = new Cesium.CallbackProperty(() => {
return 100000 * Math.sin(Date.now() / 1000 + i)
}, false)
})
在实际项目中,我发现合理使用这些交互技巧可以极大提升用户体验。特别是在处理大型地理数据集时,动态加载和筛选比一次性渲染所有数据要高效得多。