基于cesium和vue绘制多边形实现地形开挖效果,适合学习Cesium与前端框架结合开发3D可视化项目。
demo源码运行环境以及配置
运行环境:依赖Node安装环境,demo本地Node版本:推荐v18+。
运行工具:vscode或者其他工具。
配置方式:下载demo源码,vscode打开,然后顺序执行以下命令:
(1)下载demo环境依赖包命令:npm install
(2)启动demo命令:npm run dev
(3)打包demo命令: npm run build
技术栈
Vue 3.5.13
Vite 6.2.0
Cesium 1.128.0
示例效果
核心源码
<template>
<div id="cesiumContainer" class="cesium-container">
<!-- 模型调整控制面板 -->
<div class="control-panel">
<div class="panel-header">
<h3>地形开挖</h3>
</div>
<div class="panel-body">
<div class="control-group">
<button @click="startDrawPolygon">绘制多边形</button>
</div>
<div class="control-group">
<button @click="clearDrawing">清除绘制</button>
</div>
<div class="control-group" v-if="drawingInstructions">
<span>{{ drawingInstructions }}</span>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import * as Cesium from 'cesium';
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxZjhjYjhkYS1jMzA1LTQ1MTEtYWE1Mi0zODc5NDljOGVkMDYiLCJpZCI6MTAzNjEsInNjb3BlcyI6WyJhc2wiLCJhc3IiLCJhc3ciLCJnYyJdLCJpYXQiOjE1NzA2MDY5ODV9.X7tj92tunUvx6PkDpj3LFsMVBs_SBYyKbIL_G9xKESA';
// 声明Cesium Viewer实例
let viewer = null;
// 声明变量用于存储事件处理器和绘制状态
let handler = null;
let activeShapePoints = [];
let activeShape = null;
let floatingPoint = null;
let excavateInstance = null;
// 绘制状态提示
const drawingInstructions = ref('');
// 组件挂载后初始化Cesium
onMounted(async () => {
const files = [
"./excavateTerrain/excavateTerrain.js"
];
loadScripts(files, function () {
console.log("All scripts loaded");
initMap();
});
});
const loadScripts = (files, callback) => {
// Make Cesium available globally for the scripts
window.Cesium = Cesium;
if (files.length === 0) {
callback();
return;
}
const file = files.shift();
const script = document.createElement("script");
script.onload = function () {
loadScripts(files, callback);
};
script.src = file;
document.head.appendChild(script);
};
const initMap = async () => {
// 初始化Cesium Viewer
viewer = new Cesium.Viewer('cesiumContainer', {
// 基础配置
animation: false, // 动画小部件
baseLayerPicker: false, // 底图选择器
fullscreenButton: false, // 全屏按钮
vrButton: false, // VR按钮
geocoder: false, // 地理编码搜索框
homeButton: false, // 主页按钮
infoBox: false, // 信息框 - 禁用点击弹窗
sceneModePicker: false, // 场景模式选择器
selectionIndicator: false, // 选择指示器
timeline: false, // 时间轴
navigationHelpButton: false, // 导航帮助按钮
navigationInstructionsInitiallyVisible: false, // 导航说明初始可见性
scene3DOnly: false, // 仅3D场景
terrain: Cesium.Terrain.fromWorldTerrain(), // 使用世界地形
});
// 隐藏logo
viewer.cesiumWidget.creditContainer.style.display = "none";
viewer.scene.globe.enableLighting = true;
// 禁用大气层和太阳
viewer.scene.skyAtmosphere.show = false;
//前提先把场景上的图层全部移除或者隐藏
// viewer.scene.globe.baseColor = Cesium.Color.BLACK; //修改地图蓝色背景
viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.1, 0.2, 1.0); //修改地图为暗蓝色背景
// 设置抗锯齿
viewer.scene.postProcessStages.fxaa.enabled = true;
// 清除默认底图
viewer.imageryLayers.remove(viewer.imageryLayers.get(0));
// 加载底图 - 使用更暗的地图服务
const imageryProvider = await Cesium.ArcGisMapServerImageryProvider.fromUrl("https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer");
viewer.imageryLayers.addImageryProvider(imageryProvider);
// 设置默认视图位置 - 默认显示全球视图
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(104.0, 30.0, 10000000.0), // 中国中部上空
orientation: {
heading: 0.0,
pitch: -Cesium.Math.PI_OVER_TWO,
roll: 0.0
}
});
// 启用地形深度测试,确保地形正确渲染
viewer.scene.globe.depthTestAgainstTerrain = true;
// 清除默认地形
// viewer.scene.terrainProvider = new Cesium.EllipsoidTerrainProvider({});
// 开启帧率
viewer.scene.debugShowFramesPerSecond = true;
}
// 开始绘制多边形
const startDrawPolygon = () => {
// 清除之前的绘制
clearDrawing();
// 设置绘制提示
drawingInstructions.value = '左键点击添加顶点,右键完成绘制';
// 创建新的事件处理器
handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
// 监听左键点击事件 - 添加顶点
handler.setInputAction((click) => {
// 获取点击位置的笛卡尔坐标
const cartesian = viewer.scene.pickPosition(click.position);
if (!Cesium.defined(cartesian)) {
return;
}
// 将点添加到活动形状点数组
activeShapePoints.push(cartesian);
// 如果是第一个点,创建浮动点
if (activeShapePoints.length === 1) {
floatingPoint = createPoint(cartesian);
// 创建动态多边形
activeShape = createPolygon(activeShapePoints);
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 监听鼠标移动事件 - 更新动态多边形
handler.setInputAction((movement) => {
if (activeShapePoints.length >= 1) {
const cartesian = viewer.scene.pickPosition(movement.endPosition);
if (!Cesium.defined(cartesian)) {
return;
}
// 更新浮动点位置
if (floatingPoint) {
floatingPoint.position.setValue(cartesian);
}
// 更新动态多边形
if (activeShape) {
const positions = activeShapePoints.concat([cartesian]);
activeShape.polygon.hierarchy = new Cesium.PolygonHierarchy(positions);
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 监听右键点击事件 - 完成绘制
handler.setInputAction(() => {
if (activeShapePoints.length < 3) {
// 至少需要3个点才能形成多边形
drawingInstructions.value = '至少需要3个点才能形成多边形,请继续绘制';
return;
}
// 完成绘制
terminateShape();
// 执行地形开挖
performExcavation(activeShapePoints);
// 清除绘制状态
drawingInstructions.value = '绘制完成,地形已开挖';
// 清除事件处理器
handler.destroy();
handler = null;
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
};
// 创建点实体
const createPoint = (position) => {
return viewer.entities.add({
position: position,
point: {
color: Cesium.Color.WHITE,
pixelSize: 10,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
}
});
};
// 创建多边形实体
const createPolygon = (positions) => {
return viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: new Cesium.ColorMaterialProperty(Cesium.Color.WHITE.withAlpha(0.3)),
outline: true,
outlineColor: Cesium.Color.WHITE,
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
// 关键属性:贴地模式
clampToGround: true,
// 禁用挤压高度
// height: 0,
// height: 0,
// perPositionHeight: false,
// classificationType: Cesium.ClassificationType.BOTH,
// zIndex: 100
}
});
};
// 完成绘制形状
const terminateShape = () => {
// 移除动态实体
if (floatingPoint) {
viewer.entities.remove(floatingPoint);
floatingPoint = null;
}
if (activeShape) {
viewer.entities.remove(activeShape);
activeShape = null;
}
// 创建最终的多边形
// if (activeShapePoints.length >= 3) {
// const finalPolygon = viewer.entities.add({
// polygon: {
// hierarchy: new Cesium.PolygonHierarchy(activeShapePoints),
// material: new Cesium.ColorMaterialProperty(Cesium.Color.GREEN.withAlpha(0.3)),
// outline: true,
// outlineColor: Cesium.Color.GREEN,
// height: 0,
// perPositionHeight: false,
// classificationType: Cesium.ClassificationType.BOTH,
// zIndex: 100
// }
// });
// }
};
// 执行地形开挖
const performExcavation = (positions) => {
// 如果已有开挖实例,先尝试清除
if (excavateInstance) {
try {
// 重置地形开挖
viewer.scene.globe.clippingPlanes = undefined;
viewer.entities.removeById("entityDM");
viewer.entities.removeById("entityDMBJ");
} catch (error) {
console.error("清除之前的开挖失败", error);
}
}
// 创建新的地形开挖实例
excavateInstance = new excavateTerrain(viewer, {
positions: positions,
height: 50,
bottom: "./excavateTerrain/excavationregion_side.jpg",
side: "./excavateTerrain/excavationregion_top.jpg",
});
};
// 清除绘制
const clearDrawing = () => {
// 清除事件处理器
if (handler) {
handler.destroy();
handler = null;
}
// 清除动态实体
if (floatingPoint) {
viewer.entities.remove(floatingPoint);
floatingPoint = null;
}
if (activeShape) {
viewer.entities.remove(activeShape);
activeShape = null;
}
// 清空点数组
activeShapePoints = [];
viewer.entities.removeAll();
// 清除绘制提示
drawingInstructions.value = '';
// 重置地形开挖
if (excavateInstance) {
try {
viewer.scene.globe.clippingPlanes = undefined;
viewer.entities.removeById("entityDM");
viewer.entities.removeById("entityDMBJ");
excavateInstance = null;
} catch (error) {
console.error("清除地形开挖失败", error);
}
}
};
// 组件卸载前清理资源
onUnmounted(() => {
clearDrawing();
if (viewer) {
viewer.destroy();
viewer = null;
}
});
</script>
<style scoped>
.cesium-container {
width: 100%;
height: 100vh;
margin: 0;
padding: 0;
overflow: hidden;
position: relative;
}
.control-panel {
position: absolute;
top: 20px;
left: 20px;
width: 300px;
background-color: rgba(38, 38, 38, 0.85);
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
color: white;
z-index: 1000;
overflow: hidden;
transition: all 0.3s ease;
}
.panel-header {
background-color: rgba(0, 0, 0, 0.5);
padding: 10px 15px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.panel-header h3 {
margin: 0;
font-size: 16px;
font-weight: 500;
}
.panel-body {
padding: 15px;
}
.control-group {
margin-bottom: 15px;
}
.control-group label {
display: block;
margin-bottom: 5px;
font-size: 14px;
}
.control-group input[type="range"] {
width: 100%;
margin-bottom: 5px;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 4px;
}
.control-group span {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
display: block;
margin-top: 5px;
line-height: 1.4;
}
.control-group span {
font-size: 12px;
color: rgba(255, 255, 255, 0.7);
}
.control-group button {
background-color: #4285f4;
color: white;
border: none;
padding: 8px 15px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
width: 100%;
transition: background-color 0.3s ease;
}
.control-group button:hover {
background-color: #3367d6;
}
</style>