Vue 结合 Cesium 实现 3D 地图效果完整教程
·
前言
在 Web 端实现 3D 地图可视化,Cesium 无疑是目前最强大的开源解决方案之一。结合 Vue 框架,我们可以快速构建出交互丰富、性能优秀的 3D 地理信息应用。本文将带你从零开始,手把手实现一个完整的 Vue + Cesium 3D 地图项目。
效果预览:
-
加载全球 3D 地形
-
展示 3D 建筑模型
-
添加标记点和信息弹窗
-
支持自由漫游和视角控制
一、环境准备
1.1 创建 Vue 项目
# 使用 Vite 创建 Vue 3 项目 npm create vite@latest vue-cesium-map -- --template vue cd vue-cesium-map # 安装依赖 npm install
1.2 安装 Cesium
# 安装 cesium 和 vite-plugin-cesium npm install cesium npm install vite-plugin-cesium -D
1.3 配置 Vite
修改 vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import cesium from 'vite-plugin-cesium'
export default defineConfig({
plugins: [
vue(),
cesium()
],
resolve: {
alias: {
'@': '/src'
}
}
})
二、项目结构
vue-cesium-map/ ├── src/ │ ├── components/ │ │ └── CesiumMap.vue # 地图组件 │ ├── assets/ │ │ └── cesium/ # Cesium 静态资源 │ ├── App.vue │ └── main.js ├── public/ │ └── CesiumAssets/ # 地形、模型等资源 └── vite.config.js
三、核心代码实现
3.1 创建地图组件
src/components/CesiumMap.vue:
<template>
<div class="cesium-container">
<div ref="cesiumContainer" class="cesium-viewer"></div>
<!-- 控制面板 -->
<div class="control-panel">
<h3>🎮 控制面板</h3>
<div class="control-item">
<label>视角位置:</label>
<select v-model="currentView" @change="flyToView">
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="guangzhou">广州</option>
<option value="global">全球视图</option>
</select>
</div>
<div class="control-item">
<button @click="toggleLayer('terrain')">🏔️ 地形</button>
<button @click="toggleLayer('imagery')">🛰️ 影像</button>
<button @click="toggleLayer('building')">🏢 建筑</button>
</div>
</div>
<!-- 信息弹窗 -->
<div v-if="showInfo" class="info-popup" :style="popupStyle">
<h4>{{ popupData.title }}</h4>
<p>{{ popupData.content }}</p>
<button @click="showInfo = false">关闭</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as Cesium from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css'
// 配置 Cesium Ion Token(需要去 cesium.com 注册获取)
Cesium.Ion.defaultAccessToken = 'YOUR_CESIUM_ION_TOKEN'
const cesiumContainer = ref(null)
const viewer = ref(null)
const currentView = ref('beijing')
const showInfo = ref(false)
const popupData = ref({ title: '', content: '' })
const popupStyle = ref({})
// 城市坐标
const cityViews = {
beijing: { lon: 116.4074, lat: 39.9042, height: 1000 },
shanghai: { lon: 121.4737, lat: 31.2304, height: 1000 },
guangzhou: { lon: 113.2644, lat: 23.1291, height: 1000 },
global: { lon: 104.1954, lat: 35.8617, height: 10000000 }
}
// 初始化地图
onMounted(() => {
initCesium()
addMarkers()
})
onUnmounted(() => {
if (viewer.value) {
viewer.value.destroy()
}
})
function initCesium() {
viewer.value = new Cesium.Viewer(cesiumContainer.value, {
// 基础配置
animation: false, // 隐藏动画控件
timeline: false, // 隐藏时间轴
fullscreenButton: false, // 隐藏全屏按钮
homeButton: false, // 隐藏 Home 按钮
navigationHelpButton: false, // 隐藏帮助按钮
sceneModePicker: false, // 隐藏 2D/3D 切换
// 影像图层
imageryProvider: new Cesium.ArcGisMapServerImageryProvider({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
}),
// 地形配置
terrainProvider: Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
}),
// 其他配置
baseLayerPicker: true, // 显示图层选择器
geocoder: true, // 显示地名搜索
infoBox: true, // 启用信息框
selectionIndicator: true, // 显示选择指示器
})
// 隐藏 Cesium logo(仅用于学习,生产环境请保留)
viewer.value.cesiumWidget.creditContainer.style.display = 'none'
// 设置初始视角
flyToView()
}
// 添加标记点
function addMarkers() {
const markers = [
{ lon: 116.4074, lat: 39.9042, title: '北京', content: '中国首都,政治文化中心' },
{ lon: 121.4737, lat: 31.2304, title: '上海', content: '中国经济中心,国际大都市' },
{ lon: 113.2644, lat: 23.1291, title: '广州', content: '华南中心城市,千年商都' },
]
markers.forEach(marker => {
const entity = viewer.value.entities.add({
position: Cesium.Cartesian3.fromDegrees(marker.lon, marker.lat),
point: {
pixelSize: 15,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 3
},
label: {
text: marker.title,
font: '16px sans-serif',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
pixelOffset: new Cesium.Cartesian2(0, -30)
}
})
// 添加点击事件
entity._markerData = marker
})
// 处理点击事件
viewer.value.screenSpaceEventHandler.setInputAction((click) => {
const pickedObject = viewer.value.scene.pick(click.position)
if (Cesium.defined(pickedObject) && pickedObject.id) {
const entity = pickedObject.id
if (entity._markerData) {
popupData.value = entity._markerData
popupStyle.value = {
left: click.position.x + 10 + 'px',
top: click.position.y + 10 + 'px'
}
showInfo.value = true
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
// 切换视角
function flyToView() {
const view = cityViews[currentView.value]
viewer.value.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(
view.lon,
view.lat,
view.height
),
duration: 2
})
}
// 切换图层
function toggleLayer(type) {
switch(type) {
case 'terrain':
viewer.value.terrainProvider = viewer.value.terrainProvider ?
new Cesium.EllipsoidTerrainProvider() :
Cesium.createWorldTerrain()
break
case 'imagery':
viewer.value.imageryLayers.removeAll()
viewer.value.imageryLayers.addImageryProvider(
new Cesium.ArcGisMapServerImageryProvider({
url: 'https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer'
})
)
break
case 'building':
// 3D 建筑需要加载 3D Tiles
console.log('3D 建筑功能需要配置 3D Tiles 数据源')
break
}
}
</script>
<style scoped>
.cesium-container {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.cesium-viewer {
width: 100%;
height: 100%;
}
.control-panel {
position: absolute;
top: 20px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 8px;
z-index: 100;
min-width: 200px;
}
.control-panel h3 {
margin: 0 0 15px 0;
font-size: 16px;
}
.control-item {
margin-bottom: 15px;
}
.control-item select {
width: 100%;
padding: 8px;
border-radius: 4px;
border: none;
margin-top: 5px;
}
.control-item button {
margin: 5px;
padding: 8px 12px;
border: none;
border-radius: 4px;
background: #4CAF50;
color: white;
cursor: pointer;
transition: background 0.3s;
}
.control-item button:hover {
background: #45a049;
}
.info-popup {
position: absolute;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 101;
max-width: 250px;
}
.info-popup h4 {
margin: 0 0 10px 0;
color: #333;
}
.info-popup p {
margin: 0 0 10px 0;
color: #666;
font-size: 14px;
}
.info-popup button {
padding: 5px 15px;
background: #2196F3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
3.2 修改主应用
src/App.vue:
<template>
<CesiumMap />
</template>
<script setup>
import CesiumMap from './components/CesiumMap.vue'
</script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
四、高级功能扩展
4.1 加载 3D 建筑模型
// 在 initCesium 函数中添加
async function load3DBuildings() {
const tileset = await Cesium.Cesium3DTileset.fromUrl(
'https://assets.cesium.com/your-3d-tiles-url'
)
viewer.value.scene.primitives.add(tileset)
// 自动调整视角到模型
viewer.value.zoomTo(tileset)
}
4.2 添加动态轨迹线
function drawFlightPath() {
const positions = [
Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 100),
Cesium.Cartesian3.fromDegrees(121.4737, 31.2304, 100),
Cesium.Cartesian3.fromDegrees(113.2644, 23.1291, 100)
]
viewer.value.entities.add({
polyline: {
positions: positions,
width: 5,
material: new Cesium.PolylineGlowMaterialProperty({
color: Cesium.Color.CYAN,
glowPower: 0.3
})
}
})
}
4.3 实现时间轴动画
function setupTimeAnimation() {
viewer.value.timeline.zoomTo(
Cesium.JulianDate.fromIso8601('2026-01-01'),
Cesium.JulianDate.fromIso8601('2026-12-31')
)
viewer.value.clock.shouldAnimate = true
viewer.value.clock.multiplier = 3600 // 1 小时/秒
}
五、性能优化建议
5.1 按需加载资源
// 使用 LOD(Level of Detail)技术 viewer.value.scene.screenSpaceCameraController.enableTilt = true viewer.value.scene.screenSpaceCameraController.minimumZoomDistance = 100 viewer.value.scene.screenSpaceCameraController.maximumZoomDistance = 50000000
5.2 控制渲染帧率
// 非激活状态降低帧率 viewer.value.scene.requestRenderMode = true viewer.value.scene.maximumRenderTimeChange = Infinity
5.3 合理使用 Entity 和 Primitive
-
Entity API:适合少量动态对象,开发便捷
-
Primitive API:适合大量静态对象,性能更优
六、常见问题解决
❓ 问题 1:Cesium 加载缓慢
解决方案:
-
使用国内 CDN 镜像
-
开启地形缓存
-
按需加载图层
❓ 问题 2:模型显示黑色
解决方案:
viewer.value.scene.globe.enableLighting = true viewer.value.scene.globe.dayNightTint = new Cesium.Color(0, 0, 0, 0.5)
❓ 问题 3:移动端性能差
解决方案:
-
降低地形精度
-
减少同时显示的 Entity 数量
-
使用 WebGL 性能检测
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)