作为一个长期从事地理信息系统开发的工程师,我经常需要在 Web 项目中集成 3D 地图功能。Cesium 作为目前最强大的开源 3D 地球可视化库,虽然功能强大但入门门槛较高。今天我将分享在 Vue 项目中集成 Cesium 的完整方案,特别针对无 Token 的本地开发环境进行了优化。
这个方案的核心优势在于:
首先创建一个标准的 Vue 3 项目(这里以 Vite 为例):
bash复制npm create vite@latest cesium-demo --template vue
cd cesium-demo
然后安装 Cesium 核心库:
bash复制npm install cesium
注意:Cesium 的 npm 包体积较大(约 50MB),安装可能需要较长时间。如果遇到网络问题,可以考虑使用国内镜像源。
Cesium 需要一些静态资源文件才能正常运行,我们需要将这些文件复制到项目的 public 目录:
node_modules/cesium/Build/Cesium 目录public 目录下目录结构应该如下所示:
code复制public/
└── Cesium/
├── Assets/
├── ThirdParty/
├── Widgets/
├── Cesium.js
└── ...
在 public/index.html 的 <head> 部分添加以下代码:
html复制<link href="/Cesium/Widgets/widgets.css" rel="stylesheet">
<script src="/Cesium/Cesium.js"></script>
关键点:必须在 Vue 挂载的 DOM 元素之前加载 Cesium.js,否则会报初始化错误。
创建一个 MapView.vue 组件,包含以下核心功能:
vue复制<template>
<div class="map-page">
<div ref="mapContainerRef" class="map-container"></div>
</div>
</template>
<script>
export default {
name: 'MapView',
data() {
return {
viewer: null
}
},
mounted() {
this.initMap()
},
methods: {
initMap() {
const Cesium = window.Cesium
if (!Cesium) {
console.error('Cesium 未正确加载')
return
}
// 配置高德地图瓦片服务(无需 Token)
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: "https://webst0{s}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
subdomains: ["1", "2", "3", "4"],
minimumLevel: 3,
maximumLevel: 18,
})
// 初始化 Viewer
this.viewer = new Cesium.Viewer(this.$refs.mapContainerRef, {
animation: false,
timeline: false,
baseLayerPicker: false,
geocoder: false,
homeButton: false,
sceneModePicker: false,
navigationHelpButton: false,
fullscreenButton: false,
infoBox: false,
selectionIndicator: false,
vrButton: false,
projectionPicker: false,
shouldAnimate: false,
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
imageryProvider: false
})
// 手动添加高德地图图层
this.viewer.imageryLayers.removeAll()
this.viewer.imageryLayers.addImageryProvider(imageryProvider)
// 移除版权信息(开发环境)
this.viewer._cesiumWidget._creditContainer.style.display = "none"
}
},
beforeUnmount() {
if (this.viewer) {
this.viewer.destroy()
this.viewer = null
}
}
}
</script>
<style scoped>
.map-page {
position: relative;
width: 100vw;
height: 100vh;
}
.map-container {
width: 100%;
height: 100%;
}
</style>
高德地图瓦片配置:
subdomains: 使用多个子域名分摊请求,避免并发限制minimumLevel: 设置最小缩放级别(3级对应全国视图)maximumLevel: 设置最大缩放级别(18级对应街道细节)Viewer 初始化选项:
terrainProvider: 使用默认椭球地形(无需网络请求)imageryProvider: false: 阻止 Cesium 自动加载默认底图性能优化:
viewer.destroy() 释放资源创建 map.js 工具文件,实现以下功能:
javascript复制// 定位到指定坐标
export const locate = (viewer, options = {}) => {
const {
lon,
lat,
height = 2000000,
heading = 0,
pitch = -90,
duration = 1.2,
flyTo = true
} = options
const destination = Cesium.Cartesian3.fromDegrees(lon, lat, height)
const orientation = {
heading: Cesium.Math.toRadians(heading),
pitch: Cesium.Math.toRadians(pitch)
}
if (flyTo) {
viewer.camera.flyTo({ destination, orientation, duration })
} else {
viewer.camera.setView({ destination, orientation })
}
}
// 场景模式切换
export const switchSceneMode = (viewer, mode, duration = 0.8) => {
const normalizedMode = String(mode || "").toUpperCase()
if (normalizedMode === "2D") {
viewer.scene.morphTo2D(duration)
} else if (normalizedMode === "3D") {
viewer.scene.morphTo3D(duration)
} else {
viewer.scene.morphToColumbusView(duration)
}
}
// 获取相机状态
export const getCameraState = (viewer) => {
const cartographic = viewer.camera.positionCartographic
return {
height: cartographic.height,
heading: Cesium.Math.toDegrees(viewer.camera.heading),
pitch: Cesium.Math.toDegrees(viewer.camera.pitch),
lon: Cesium.Math.toDegrees(cartographic.longitude),
lat: Cesium.Math.toDegrees(cartographic.latitude)
}
}
创建 MapTools.vue 组件提供 UI 控制界面:
vue复制<template>
<div class="map-tools">
<el-card shadow="always">
<el-button-group>
<el-button @click="onLocate">定位到北京</el-button>
<el-button @click="onZoomIn">放大</el-button>
<el-button @click="onZoomOut">缩小</el-button>
</el-button-group>
<el-button-group style="margin-left: 10px">
<el-button @click="on2D">2D</el-button>
<el-button @click="on3D">3D</el-button>
</el-button-group>
<el-descriptions :column="1" border>
<el-descriptions-item label="经度">{{ cameraState?.lon.toFixed(6) }}</el-descriptions-item>
<el-descriptions-item label="纬度">{{ cameraState?.lat.toFixed(6) }}</el-descriptions-item>
<el-descriptions-item label="高度">{{ cameraState?.height.toFixed(2) }}米</el-descriptions-item>
</el-descriptions>
</el-card>
</div>
</template>
<script>
import { locate, getCameraState, switchSceneMode } from './map'
export default {
props: ['viewer'],
data() {
return {
cameraState: null
}
},
methods: {
onLocate() {
locate(this.viewer, { lon: 116.4074, lat: 39.9042 })
this.refreshCameraState()
},
refreshCameraState() {
this.cameraState = getCameraState(this.viewer)
}
// 其他方法...
}
}
</script>
<style scoped>
.map-tools {
position: absolute;
top: 20px;
left: 20px;
z-index: 999;
}
</style>
在 map.js 中添加区域飞行功能:
javascript复制export const flyToPointsRegion = (viewer, points, options = {}) => {
const cartesians = points.map(p =>
Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height || 0)
)
const boundingSphere = Cesium.BoundingSphere.fromPoints(cartesians)
const range = boundingSphere.radius * (options.rangePaddingRatio || 1.2)
viewer.camera.flyToBoundingSphere(boundingSphere, {
duration: options.duration || 1.2,
offset: new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(options.heading || 0),
Cesium.Math.toRadians(options.pitch || -45),
range
)
})
}
javascript复制// 飞向北京五环区域
const beijingPoints = [
{ lon: 116.403963, lat: 39.915119 },
{ lon: 116.397026, lat: 39.908722 },
{ lon: 116.323171, lat: 39.983607 },
{ lon: 116.486409, lat: 39.921489 }
]
flyToPointsRegion(viewer, beijingPoints, {
pitch: -30,
duration: 2.5
})
空白页面:
Token 相关错误:
imageryProvider: false 避免自动加载 Bing 地图跨域问题:
按需加载:
javascript复制// 动态导入 Cesium 模块
const { Viewer } = await import('cesium')
地形数据优化:
javascript复制// 使用简单地形替代
viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()
内存管理:
javascript复制// 定期清理实体
viewer.entities.removeAll()
完整的项目结构建议:
code复制src/
├── components/
│ ├── MapView.vue # 主地图组件
│ └── MapTools.vue # 地图控制工具
├── utils/
│ └── map.js # 地图工具函数
└── assets/
└── styles/
└── map.scss # 地图相关样式
扩展功能建议:
在实际项目中,我发现这套架构可以支持中等复杂度的 GIS 应用开发。特别是在政务、应急管理等领域的可视化系统中表现良好,既能满足 3D 展示需求,又保持了 Vue 的开发体验。