绘制图形并导出geojson ·Draw and export geojson· ▶ 在线运行案例
- 案例合集:三维可视化功能案例(threehub.cn)
- 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
- 400个案例代码:网盘链接
你将学到什么
- Cesium Entity 高层实体 API
- GeoJSON 矢量面/线/点加载
效果说明
本案例演示绘制图形并导出geojson效果:Cesium绘图并导出GeoJSON工具,本工具提供交互式点、线、面绘制功能,并能导出为标准GeoJSON格式;核心用到 Cesium、GeoJSON。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。
核心概念
- Viewer聚合 Scene、Camera、Clock 与渲染循环,是 Cesium 应用入口。
- Entity面向点线面/模型/标签的高层 API;与 Primitive 相比更适合交互与属性驱动。
- 阅读下方完整源码时,建议从
init/load/animate三条主线入手,再深入 shader 与工具函数。
实现步骤
- 创建 Viewer,配置地形/影像(若案例需要)并设置初始相机
- 在
requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染) 代码要点
/**- Cesium绘图并导出GeoJSON工具
- 本工具提供交互式点、线、面绘制功能,并能导出为标准GeoJSON格式
- * 功能说明:
- 1. 用户点击"绘制点/线/面"按钮开始交互式绘制
- 2. 在地图上点击选择点位置
- 3. 点击"导出为Geojson"按钮将绘制内容导出为GeoJSON文件
- * @module DrawAndExportGeojson
- @author z2586300277
- @version 1.0.0
- @since 2024
// ==================== 导入模块和初始化 ==================== /**
- 导入Cesium库,用于3D地球可视化和地理空间数据处理
- @external Cesium
- @see {@link https://cesium.com/}
/**
- 导入dat.GUI库,用于创建轻量级的图形用户界面控制面板
- @external GUI
- @see {@link https://github.com/dataarts/dat.gui}
/**
- 获取HTML中用于挂载Cesium Viewer的地图容器元素
- @type {HTMLElement}
- @constant
// ==================== GUI控制面板 ==================== /**
- 定义图形绘制操作对象,包含所有GUI控制面板的功能按钮
- @namespace obj
- @property {Function} '绘制点' - 启动交互式点绘制模式
- @property {Function} '绘制线' - 启动交互式线绘制模式
- @property {Function} '绘制面' - 启动交互式面绘制模式
- @property {Function} '全部清除' - 清除所有已绘制的实体
- @property {Function} '导出为Geojson' - 将绘制内容导出为GeoJSON格式文件
- 绘制点功能 - 启动交互式点绘制模式
- 当用户点击此按钮时,激活点绘制模式,允许用户在地图上点击添加点标记
- @function
- @memberof obj
- @example
- // 点击按钮后,用户可以在地图上点击添加点
- // 每次点击都会创建一个新的点实体
- 绘制线功能 - 启动交互式线绘制模式
- 当用户点击此按钮时,激活线绘制模式,允许用户通过连续点击创建线段
- @function
- @memberof obj
- @example
- // 点击按钮后,用户可以通过连续点击创建折线
- // 右键结束绘制操作
- 绘制面功能 - 启动交互式面绘制模式
- 当用户点击此按钮时,激活面绘制模式,允许用户通过连续点击创建多边形
- @function
- @memberof obj
- @example
- // 点击按钮后,用户可以通过连续点击创建多边形
- // 至少需要3个点才能形成面,右键结束绘制操作
- 全部清除功能 - 清除所有已绘制的实体
- 移除地图上所有通过本工具创建的点、线、面等实体
- @function
- @memberof obj
- 导出为Geojson功能 - 将绘制内容导出为GeoJSON格式文件
- 将当前地图上所有绘制的实体转换为GeoJSON格式并提供下载
- @function
- @memberof obj
- @example
- // 点击后会生成一个GeoJSON文件供用户下载
- // 文件包含所有点、线、面的地理坐标信息
/**
- 创建图形用户界面控制面板实例
- 使用dat.GUI库创建一个可折叠的控制面板,用于访问绘图工具的各种功能
- @type {GUI}
- @see {@link https://github.com/dataarts/dat.gui}
// ==================== Cesium Viewer初始化 ==================== /**
- 初始化Cesium Viewer实例,这是整个应用的核心组件
- Viewer是Cesium的主要_widget_,提供了完整的3D地球可视化功能
- @type {Cesium.Viewer}
- @constant
- @see {@link https://cesium.com/learn/cesiumjs/ref-doc/Viewer.html}
/**
- 隐藏Cesium默认的Logo信息和版权信息
- 通过修改CSS样式隐藏右下角的Cesium Ion logo和相关版权信息
// ==================== 全局变量定义 ==================== /**
- 绘图事件处理器实例,用于处理鼠标交互事件
/**
- 存储当前正在绘制的点坐标数组
- 在绘制线或面时,存储用户已点击确认的点坐标
- @type {Array }
// ==================== 配置参数 ==================== const CONFIG = { /**
- 点样式配置
- @memberof CONFIG
- @property {number} pixelSize - 点的像素大小
- @property {Cesium.Color} color - 点的填充颜色
- @property {Cesium.Color} outlineColor - 点的边框颜色
- @property {number} outlineWidth - 点的边框宽度
- 线样式配置
- @memberof CONFIG
- @property {number} width - 线的宽度(像素)
- @property {Cesium.Color} material - 线的材质颜色
- 面样式配置
- @memberof CONFIG
- @property {Cesium.Color} material - 面的填充材质
- @property {boolean} outline - 是否显示边框
- @property {Cesium.Color} outlineColor - 边框颜色
- @property {number} outlineWidth - 边框宽度
- 临时线样式配置(用于绘制过程中的预览线)
- @memberof CONFIG
- @property {number} width - 线的宽度(像素)
- @property {Cesium.Color} material - 线的材质颜色(带透明度)
// ==================== 图形创建函数 ==================== /**
- 创建点标记实体
- 在指定的地理坐标位置创建一个点标记
/**
- 创建线段实体
- 根据给定的点坐标数组创建线段实体
/**
- 创建多边形实体
- 根据给定的点坐标数组创建多边形实体
// ==================== 绘图控制函数 ==================== /**
- 清除当前绘制状态
- 销毁当前的绘图事件处理器并重置活动点数组
- 在开始新的绘制操作前调用此函数以确保状态干净
/**
- 初始化绘图处理器
- 创建并配置用于处理鼠标交互事件的处理器
- @param {Function} clickHandler - 鼠标左键点击事件处理函数
- @param {Function} moveHandler - 鼠标移动事件处理函数
- @param {Function} endHandler - 结束绘制事件处理函数(通常是右键点击)
- @example
- // 初始化一个只处理点击事件的处理器
- initDrawingHandler(clickFunction, null, null);
- * // 初始化处理点击、移动和结束事件的完整处理器
- initDrawingHandler(clickFunction, moveFunction, endFunction);
// 创建事件处理器实例,绑定到Viewer的canvas元素 drawHandler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);
// 注册鼠标左键点击事件处理函数(用于添加点) if (clickHandler) { drawHandler.setInputAction(clickHandler, Cesium.ScreenSpaceEventType.LEFT_CLICK); }
// 注册鼠标移动事件处理函数(用于实时预览) if (moveHandler) { drawHandler.setInputAction(moveHandler, Cesium.ScreenSpaceEventType.MOUSE_MOVE); }
// 注册鼠标右键点击事件处理函数(用于结束绘制) if (endHandler) { drawHandler.setInputAction(endHandler, Cesium.ScreenSpaceEventType.RIGHT_CLICK); } }
/**
- 绘制点功能实现
- 激活点绘制模式,用户每次左键点击都会在地图上创建一个点标记
- 此模式不需要结束操作,可以连续点击创建多个点
- 初始化点绘制处理器
- 只需要处理点击事件,不需要处理移动和结束事件
/**
- 绘制线功能实现
- 激活线绘制模式,用户通过连续左键点击创建折线,右键结束绘制
- 绘制过程中会显示临时线段预览
- 临时线段实体,用于在绘制过程中显示预览效果
- @type {Cesium.Entity|null}
/**
- 初始化线绘制处理器
- 需要处理点击、移动和结束事件
// 如果至少有两个点,创建线段连接最后两个点 if (activePoints.length >= 2) { createLine([ activePoints[activePoints.length - 2], activePoints[activePoints.length - 1] ]); } } }, // 鼠标移动处理函数 - 实时更新临时线段预览 function (movement) { // 获取鼠标当前位置的笛卡尔坐标 const cartesian = viewer.scene.pickPosition(movement.endPosition); // 如果已有至少一个点且能获取到当前位置 if (cartesian && activePoints.length > 0) { if (tempLineEntity) { // 更新临时线段的端点 tempLineEntity.polyline.positions = [activePoints[activePoints.length - 1], cartesian]; } else { // 创建新的临时线段 tempLineEntity = createLine([activePoints[activePoints.length - 1], cartesian], true); } } }, // 右键结束处理函数 - 完成线绘制操作 function () { // 清除临时实体 if (tempLineEntity) { viewer.entities.remove(tempLineEntity); tempLineEntity = null; }
// 销毁事件处理器 if (drawHandler) { drawHandler.destroy(); drawHandler = null; } // 重置活动点数组 activePoints = []; } ); }
/**
- 绘制面功能实现
- 激活面绘制模式,用户通过连续左键点击创建多边形,右键结束绘制
- 绘制过程中会显示临时面预览
- 临时面实体,用于在绘制过程中显示预览效果
- @type {Cesium.Entity|null}
- 临时点标记数组,存储绘制过程中创建的点标记
- @type {Array }
- 初始化面绘制处理器
- 需要处理点击、移动和结束事件
// 创建点标记以可视化顶点位置 tempPoints.push(createPointMarker(cartesian)); // 如果已经有面实体,则更新它 if (tempPlaneEntity) { // 更新面的顶点 tempPlaneEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(activePoints); } else if (activePoints.length >= 3) { // 创建新的面实体(至少需要3个点) tempPlaneEntity = createPolygon(activePoints); } } }, // 鼠标移动处理函数 - 实时更新临时面预览 function (movement) { // 获取鼠标当前位置的笛卡尔坐标 const cartesian = viewer.scene.pickPosition(movement.endPosition); // 如果已有至少两个点且能获取到当前位置 if (cartesian && activePoints.length >= 2) { // 创建临时点数组,包含所有已确认的点和当前鼠标位置 const tempPositions = [...activePoints, cartesian];
if (tempPlaneEntity) { // 更新面的顶点 tempPlaneEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(tempPositions); } else if (tempPositions.length >= 3) { // 创建新的面实体(至少需要3个点) tempPlaneEntity = createPolygon(tempPositions); } } }, // 右键结束处理函数 - 完成面绘制操作 function () { // 如果点数少于3个,移除临时实体(无法构成面) if (activePoints.length < 3 && tempPlaneEntity) { viewer.entities.remove(tempPlaneEntity); tempPlaneEntity = null; }
// 更新面实体的最终顶点 if (tempPlaneEntity) { tempPlaneEntity.polygon.hierarchy = new Cesium.PolygonHierarchy(activePoints); } // 移除所有临时点标记 tempPoints.forEach(element => { viewer.entities.remove(element) }); // 销毁事件处理器 if (drawHandler) { drawHandler.destroy(); drawHandler = null; } // 重置临时数组 tempPoints = []; activePoints = []; } ); }
// ==================== GeoJSON导出功能 ==================== /**
- 导出为GeoJSON格式文件
- 将当前地图上所有绘制的实体转换为GeoJSON格式并提供下载
- 支持点、线、面三种几何类型的导出
// 遍历所有绘制的实体并转换为GeoJSON要素 viewer.entities.values.forEach(entity => { const feature = convertEntityToGeoJsonFeature(entity); if (feature) { geojson.features.push(feature); } }); // 如果有可导出的要素,则创建下载文件 if (geojson.features.length > 0) { // 将GeoJSON对象转换为格式化的JSON字符串 const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(geojson, null, 2)); // 创建临时下载链接元素 const downloadAnchorNode = document.createElement('a'); downloadAnchorNode.setAttribute("href", dataStr); downloadAnchorNode.setAttribute("download", "cesium_drawing.geojson"); // 设置下载文件名 document.body.appendChild(downloadAnchorNode); // 添加到DOM中 // 触发点击事件开始下载 downloadAnchorNode.click(); downloadAnchorNode.remove(); // 下载完成后移除临时元素 } else { // 如果没有可导出的实体,提示用户 alert('没有可导出的实体') } }
/**
- 将Cesium实体转换为GeoJSON要素
- 根据实体类型(点、线、面)提取相应的地理坐标信息并构造成GeoJSON格式
- @param {Cesium.Entity} entity - 需要转换的Cesium实体
- @returns {Object|null} GeoJSON要素对象,如果无法转换则返回null
- @example
- const entity = viewer.entities.getById('point1');
- const feature = convertEntityToGeoJsonFeature(entity);
- // 返回类似:
- // {
- // type: "Feature",
- // properties: { id: "point1", name: "" },
- // geometry: {
- // type: "Point",
- // coordinates: [-122.0, 37.5]
- // }
- // }
// 处理点实体 if (entity.position && entity.point) { // 获取实体当前位置(考虑时间动态性) const position = entity.position.getValue(Cesium.JulianDate.now()); // 将笛卡尔坐标转换为大地坐标(弧度制) const cartographic = Cesium.Cartographic.fromCartesian(position); if (cartographic) { // 构造点几何信息(转换为度数) feature.geometry = { type: "Point", coordinates: [ Cesium.Math.toDegrees(cartographic.longitude), // 经度 Cesium.Math.toDegrees(cartographic.latitude) // 纬度 ] }; return feature; // 返回构造好的点要素 } }
// 处理线实体 if (entity.polyline) { // 获取线的所有顶点坐标 const positions = entity.polyline.positions.getValue(Cesium.JulianDate.now()); // 确保有有效的坐标数据 if (positions && positions.length > 0) { // 将所有笛卡尔坐标转换为经纬度坐标 const coordinates = positions.map(position => { const cartographic = Cesium.Cartographic.fromCartesian(position); return [ Cesium.Math.toDegrees(cartographic.longitude), // 经度 Cesium.Math.toDegrees(cartographic.latitude) // 纬度 ]; });
// 构造线几何信息 feature.geometry = { type: "LineString", coordinates: coordinates }; return feature; // 返回构造好的线要素 } }
// 处理面实体 if (entity.polygon) { // 获取面的层级结构(包含顶点信息) const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now()); // 确保有有效的顶点数据 if (hierarchy && hierarchy.positions && hierarchy.positions.length > 0) { // 构造面的坐标数组(GeoJSON面需要嵌套数组) const coordinates = [[]]; // 转换所有顶点坐标 hierarchy.positions.forEach(position => { const cartographic = Cesium.Cartographic.fromCartesian(position); coordinates[0].push([ Cesium.Math.toDegrees(cartographic.longitude), // 经度 Cesium.Math.toDegrees(cartographic.latitude) // 纬度 ]); });
// 闭合多边形(将第一个点添加到末尾以闭合面) if (hierarchy.positions.length > 0) { const firstPos = hierarchy.positions[0]; const cartographic = Cesium.Cartographic.fromCartesian(firstPos); coordinates[0].push([ Cesium.Math.toDegrees(cartographic.longitude), // 经度 Cesium.Math.toDegrees(cartographic.latitude) // 纬度 ]); }
// 构造面几何信息 feature.geometry = { type: "Polygon", coordinates: coordinates }; return feature; // 返回构造好的面要素 } }
// 如果实体不是点、线或面,则返回null return null; }完整源码:GitHub
小结
- 本文提供绘制图形并导出geojson完整 Cesium.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
- 更多 Cesium.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库