在 WebGIS 领域,二维地图已无法满足复杂的空间分析需求 —— 三维地球(3D Globe)能直观展示地理要素的空间位置、高度关系和地形特征。Cesium.js 是目前最主流的开源三维地球开发框架,支持高精度地形、影像和矢量要素渲染;而 Turf.js 则专注于空间分析算法,无需后端依赖即可完成缓冲区、距离计算、空间关系判断等核心操作。本文将通过三维缓冲区分析实战案例,带你掌握 Turf.js 与 Cesium.js 的结合使用方法,实现 “二维空间分析 + 三维可视化” 的完整流程,覆盖从环境搭建到三维要素渲染的全链路。
一、技术栈说明
- 框架:Vue3(Composition API +
<script setup>) - 三维可视化:Cesium.js(v1.118+,核心能力:三维地球渲染、地理实体管理、相机控制)
- 空间分析:Turf.js(@turf/turf v7+,核心 API:
point、buffer) - UI 组件库:Element Plus(滑块、按钮、卡片)
- 样式:Less(模块化样式管理)
- 核心功能:三维地球初始化、中心点标记、动态缓冲区生成(Turf.js 计算)、缓冲区三维渲染、相机定位到目标区域
二、环境搭建(关键步骤)
Cesium.js 的环境配置与常规前端库不同,需注意资源加载和 Token 配置:
1. 安装依赖
# 1. 初始化Vue3项目(如需新建) npm create vite@latest cesium-turf-demo -- --template vue cd cesium-turf-demo npm install # 2. 安装核心依赖 npm install @turf/turf element-plus @element-plus/icons-vue cesium --save npm install less less-loader --save-dev # 3. 配置Cesium(Vite专属,解决资源加载问题) npm install vite-plugin-cesium --save-dev2. Vite 配置(vite.config.js)
Cesium 依赖大量静态资源(如瓦片、CSS、WebAssembly),需通过插件配置:
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import cesium from 'vite-plugin-cesium' // 引入Cesium插件 export default defineConfig({ plugins: [vue(), cesium()], // 注册插件 server: { open: true, // 启动时自动打开浏览器 port: 3000 } })3. Cesium Token 配置
Cesium Ion(影像 / 地形服务)需要 Token,可前往Cesium 官网免费申请,本文提供测试 Token(仅供演示)。
三、核心功能实现:三维缓冲区分析组件
1. 组件完整代码(可直接复用)
<template> <div class="three-d-geo-fence"> <div class="header"> <h2>Turfjs+Cesium.js:三维地球中的空间分析实战</h2> </div> <div class="container"> <!-- 左侧控制面板 --> <div class="control-panel"> <el-card class="box-card"> <template #header> <div class="card-header"> <span>参数设置</span> </div> </template> <!-- 缓冲区半径滑块 --> <div class="form-item"> <span class="label">缓冲区半径 (km): {{ bufferRadius }}</span> <el-slider v-model="bufferRadius" :min="1" :max="500" @change="updateBuffer" style="margin-top: 8px" /> </div> <!-- 定位按钮 --> <div class="form-item"> <el-button type="primary" @click="flyToLocation" >定位到目标</el-button > </div> <!-- 信息展示 --> <div class="info"> <p> 中心点坐标: [{{ centerPoint[0] }}, {{ centerPoint[1] }}] </p> <p>分析结果: 已生成 {{ bufferRadius }}km 缓冲区</p> </div> </el-card> </div> <!-- Cesium三维地球容器 --> <div id="cesiumContainer" class="cesium-container"></div> </div> </div> </template> <script setup> import { onMounted, ref, onBeforeUnmount } from "vue"; import * as Cesium from "cesium"; import "cesium/Build/Cesium/Widgets/widgets.css"; // 引入Cesium样式 import * as turf from "@turf/turf"; // --- 1. Cesium配置 --- // Cesium Ion Token(测试用,生产环境请替换为自己的Token) Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjODAxMzY4Ny0wZDQ4LTQzZTAtYTFjOC04NTczZmU1MGYxNGEiLCJpZCI6MTI2MjgzLCJpYXQiOjE3NjY3MzAyMjN9.7VIKK31vj39k3Mbp4MxSxNAzOE3ggDo-n4zJPatzkkE"; // --- 2. 状态管理 --- const viewer = ref(null); // Cesium Viewer实例 const bufferRadius = ref(50); // 缓冲区半径(默认50km) const centerPoint = ref([116.3974, 39.9093]); // 中心点坐标(北京天安门) let bufferEntity = null; // 缓冲区实体 let centerEntity = null; // 中心点实体 // --- 3. 生命周期钩子 --- // 组件挂载时初始化Cesium onMounted(() => { initCesium(); }); // 组件卸载时销毁Cesium实例(防止内存泄漏) onBeforeUnmount(() => { if (viewer.value) { viewer.value.destroy(); // 销毁Viewer实例 viewer.value = null; } }); // --- 4. 核心方法:初始化Cesium三维地球 --- const initCesium = () => { // 创建Cesium Viewer实例 viewer.value = new Cesium.Viewer("cesiumContainer", { terrainProvider: new Cesium.EllipsoidTerrainProvider(), // 禁用地形(简化演示) // 隐藏不必要的控件,简化界面 animation: false, // 动画控件 timeline: false, // 时间轴 geocoder: false, // 地理编码搜索框 homeButton: false, // 主页按钮 sceneModePicker: false, // 2D/3D切换按钮 baseLayerPicker: false, // 图层选择器 navigationHelpButton: false, // 帮助按钮 infoBox: false, // 信息弹窗 selectionIndicator: false, // 选择指示器 shouldAnimate: false, // 禁用地球自转/动画 }); // 强制关闭时钟动画(防止地球自转) viewer.value.clock.shouldAnimate = false; // 隐藏版权信息(演示用,生产环境建议保留) viewer.value._cesiumWidget._creditContainer.style.display = "none"; // 添加中心点标记 addCenterPoint(); // 初始化缓冲区 updateBuffer(); // 相机定位到目标区域 flyToLocation(); }; // --- 5. 核心方法:添加中心点标记 --- const addCenterPoint = () => { // 移除旧的中心点实体(防止重复) if (centerEntity) { viewer.value.entities.remove(centerEntity); } // 创建中心点实体 centerEntity = viewer.value.entities.add({ position: Cesium.Cartesian3.fromDegrees( centerPoint.value[0], // 经度 centerPoint.value[1] // 纬度 ), // 点样式配置 point: { pixelSize: 10, // 像素大小 color: Cesium.Color.RED, // 填充色 outlineColor: Cesium.Color.WHITE, // 轮廓色 outlineWidth: 2, // 轮廓宽度 }, // 标签配置 label: { text: "中心点", // 标签文字 font: "14pt sans-serif", // 字体 style: Cesium.LabelStyle.FILL_AND_OUTLINE, // 填充+轮廓 outlineWidth: 2, // 轮廓宽度 verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 垂直对齐(底部) pixelOffset: new Cesium.Cartesian2(0, -9), // 像素偏移(避免遮挡点) }, }); }; // --- 6. 核心方法:生成并渲染缓冲区(Turf.js + Cesium) --- const updateBuffer = () => { // 步骤1:使用Turf.js生成二维缓冲区(核心空间分析逻辑) const point = turf.point(centerPoint.value); // 创建Turf点要素 const buffered = turf.buffer( point, // 中心点 bufferRadius.value, // 半径 { units: "kilometers" } // 单位:公里 ); // 步骤2:移除旧的缓冲区实体(防止重复渲染) if (bufferEntity) { viewer.value.entities.remove(bufferEntity); } // 步骤3:解析Turf生成的GeoJSON坐标,转换为Cesium格式 // Turf.buffer返回Polygon,coordinates[0]为外环坐标 const coordinates = buffered.geometry.coordinates[0]; // 扁平化坐标数组:[lon1, lat1, lon2, lat2, ...](Cesium要求格式) const cesiumPositions = coordinates.flat(); // 步骤4:在Cesium中创建缓冲区多边形实体 bufferEntity = viewer.value.entities.add({ polygon: { // 多边形轮廓(从经纬度数组创建) hierarchy: new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray(cesiumPositions) ), // 填充样式 material: Cesium.Color.RED.withAlpha(0.3), // 红色半透明 outline: true, // 显示轮廓 outlineColor: Cesium.Color.RED, // 轮廓色 height: 0, // 高度(0表示贴地) }, }); }; // --- 7. 核心方法:相机定位到目标区域 --- const flyToLocation = () => { // 根据缓冲区半径计算相机距离(保证能看到完整缓冲区) const range = bufferRadius.value * 1000 * 4; // 半径(米) * 4倍 // 相机飞行到目标位置 viewer.value .flyTo(centerEntity, { duration: 2.0, // 飞行时长(秒) offset: new Cesium.HeadingPitchRange( Cesium.Math.toRadians(0.0), // 航向(0表示正北) Cesium.Math.toRadians(-90.0), // 俯仰角(-90表示垂直向下) range // 相机到目标的距离(米) ), }) .then((result) => { if (result) { console.log("相机定位完成"); } }); }; </script> <style scoped lang="less"> .three-d-geo-fence { height: 100vh; // 占满视口高度 display: flex; flex-direction: column; overflow: hidden; .header { padding: 10px 20px; background-color: #f5f7fa; border-bottom: 1px solid #e4e7ed; h2 { margin: 0; font-size: 18px; color: #303133; font-weight: 600; } } .container { flex: 1; position: relative; overflow: hidden; .control-panel { position: absolute; top: 20px; left: 20px; z-index: 100; // 确保在三维地球上方 width: 300px; .box-card { background: rgba(255, 255, 255, 0.9); // 半透明白色背景 border: 1px solid #e4e7ed; } .card-header { font-weight: 600; color: #303133; } .form-item { margin-bottom: 15px; } .label { display: block; margin-bottom: 5px; font-weight: 600; color: #606266; font-size: 14px; } .info { margin-top: 20px; padding-top: 10px; border-top: 1px dashed #e4e7ed; color: #606266; font-size: 14px; line-height: 1.6; } } .cesium-container { position: absolute; inset: 0; // 等同于top/right/bottom/left: 0 width: 100%; height: 100%; } } } </style>2. 核心代码深度解析
(1)Turf.js 与 Cesium 的核心衔接逻辑
// 1. Turf.js生成二维缓冲区(地理坐标系) const point = turf.point(centerPoint.value); // [lng, lat] const buffered = turf.buffer(point, bufferRadius.value, { units: "kilometers" }); // 2. 坐标格式转换(Turf GeoJSON → Cesium Cartesian3) const coordinates = buffered.geometry.coordinates[0]; // Polygon外环坐标 const cesiumPositions = coordinates.flat(); // 扁平化数组 [lon1, lat1, lon2, lat2...] const hierarchy = new Cesium.PolygonHierarchy( Cesium.Cartesian3.fromDegreesArray(cesiumPositions) // 经纬度转笛卡尔坐标 ); // 3. Cesium渲染三维多边形 bufferEntity = viewer.value.entities.add({ polygon: { hierarchy: hierarchy, material: Cesium.Color.RED.withAlpha(0.3), outline: true } });- Turf.js 职责:专注于空间分析算法(缓冲区计算),输出标准 GeoJSON;
- Cesium 职责:将 GeoJSON 坐标转换为三维笛卡尔坐标,渲染为可视化实体;
- 核心转换:
Cesium.Cartesian3.fromDegreesArray()是经纬度(地理坐标系)转笛卡尔坐标(三维空间坐标系)的关键 API。
(2)Cesium 实体管理核心
Cesium 通过Entity(实体)管理地理要素,核心规则:
- 唯一标识:每个实体创建后需保存引用,便于后续移除 / 更新;
- 避免重复:更新缓冲区时先移除旧实体,再创建新实体;
- 样式配置:
point:配置点要素的大小、颜色、轮廓;label:配置文字标签的位置、字体、对齐方式;polygon:配置多边形的填充色、轮廓、高度(height: 0表示贴地)。
(3)相机控制核心
flyTo是 Cesium 相机控制的核心 API,参数说明:
viewer.value.flyTo(centerEntity, { duration: 2.0, // 飞行时长(秒) offset: new Cesium.HeadingPitchRange( 0, // 航向(0=正北,顺时针为正) Cesium.Math.toRadians(-90), // 俯仰角(-90=垂直向下看) range // 相机到目标的距离(米) ) });- 距离计算:
range = 半径(km) * 1000 * 4,保证相机距离足够远,能完整显示缓冲区; - 俯仰角:
-90°表示垂直向下(2D 视角),0°表示水平(3D 视角),可根据需求调整。
(4)性能优化要点
- 销毁实例:组件卸载时调用
viewer.destroy(),释放内存,防止内存泄漏; - 禁用动画:关闭
shouldAnimate和时钟动画,避免不必要的性能消耗; - 简化地形:使用
EllipsoidTerrainProvider(椭球体地形)替代默认地形,减少渲染压力; - 隐藏控件:移除不必要的 UI 控件,降低 DOM 渲染开销。
四、功能效果演示
1. 基础效果
- 启动项目后,页面显示三维地球,自动定位到北京天安门区域;
- 地球中心显示红色中心点标记(标注 “中心点”);
- 中心点周围显示 50km 半径的红色半透明缓冲区多边形;
- 左侧控制面板可调整缓冲区半径(1~500km),调整后缓冲区实时更新;
- 点击 “定位到目标” 按钮,相机会重新飞行到目标区域,适配新的缓冲区大小。
2. 交互效果
| 操作 | 效果 |
|---|---|
| 拖动滑块调整半径 | 缓冲区大小实时变化,信息区显示当前半径 |
| 点击 “定位到目标” | 相机平滑飞行到中心点上方,完整显示缓冲区 |
| 鼠标操作地球 | 可旋转、缩放、平移三维地球,查看缓冲区的三维效果 |
3. 关键参数示例
- 半径 50km:缓冲区覆盖北京市主城区;
- 半径 100km:缓冲区覆盖北京市 + 部分周边城市(天津、廊坊);
- 半径 500km:缓冲区覆盖华北大部分区域(北京、天津、河北、山东、山西等)。
五、代码仓库地址
完整代码已上传至 Gitee,可直接克隆运行:https://gitee.com/tang-yunyan-syp/turfjs-vue3-demo.git
六、专栏地址
本文已同步至 CSDN 专栏,可查看更多 Turf.js 实战内容:https://blog.csdn.net/m0_72065108/article/details/155226062?spm=1001.2014.3001.5501
七、实战拓展方向
更多空间分析功能:
- 距离计算:计算两个点在三维地球中的实际距离;
- 空间关系判断:判断点是否在缓冲区内(Turf.js
booleanPointInPolygon); - 要素裁剪:结合 Turf.js 的
bboxClip裁剪三维多边形; - 多要素分析:加载多个点,批量生成缓冲区并渲染。
三维效果增强:
- 地形贴合:启用 Cesium 地形服务,让缓冲区贴合真实地形;
- 高度设置:给缓冲区设置高度(如
height: 1000),实现三维围栏; - 样式自定义:支持缓冲区颜色、透明度、轮廓宽度的动态调整;
- 动态效果:添加缓冲区的显隐动画、颜色渐变效果。
交互能力扩展:
- 点选功能:点击地球任意位置,将中心点移动到点击位置;
- 导入导出:支持导入 GeoJSON 点要素,导出缓冲区 GeoJSON;
- 多区域对比:同时显示多个中心点的缓冲区,支持切换 / 隐藏;
- 测量工具:添加距离 / 面积测量工具,验证缓冲区大小。
性能优化:
- 批量渲染:使用
Primitive替代Entity,优化大量要素的渲染性能; - 层级加载:根据相机距离动态调整缓冲区的精度(远小近大);
- 异步加载:空间分析逻辑放入 Web Worker,避免阻塞主线程。
- 批量渲染:使用
八、常见问题排查
Cesium 初始化失败:
- 原因:未配置
vite-plugin-cesium,或 Token 无效; - 解决方案:检查
vite.config.js是否注册 Cesium 插件,替换为有效 Token。
- 原因:未配置
缓冲区不显示:
- 原因:坐标格式错误(Turf 为
[lng, lat],Cesium 同理); - 解决方案:确保坐标顺序为 “经度在前,纬度在后”,检查
coordinates.flat()是否正确扁平化数组。
- 原因:坐标格式错误(Turf 为
地球自转 / 抖动:
- 原因:未禁用
shouldAnimate或时钟动画; - 解决方案:设置
viewer.value.clock.shouldAnimate = false,并禁用地形动画。
- 原因:未禁用
相机定位不准确:
- 原因:
range值过小,或俯仰角设置不当; - 解决方案:增大
range倍数(如改为 5 倍),调整俯仰角为-90°(垂直向下)。
- 原因:
组件卸载后内存泄漏:
- 原因:未调用
viewer.destroy(); - 解决方案:在
onBeforeUnmount钩子中销毁 Cesium 实例,清空实体引用。
- 原因:未调用
总结
本文通过 Turf.js + Cesium.js 的结合,实现了 “二维空间分析 + 三维可视化” 的核心能力:
- 掌握了 Cesium.js 的基础配置、实体管理、相机控制等核心技能;
- 实现了 Turf.js 空间分析结果(缓冲区)到 Cesium 三维实体的转换;
- 完成了可交互的三维缓冲区分析组件,支持参数调整、实时更新、相机定位;
- 梳理了两者结合的核心逻辑和性能优化要点,为复杂三维空间分析打下基础。