Cesium 计算新坐标教程

Cesium 计算新坐标教程

计算新坐标 ·computerNewPoint· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • Cesium Viewer 初始化与场景配置
  • Cesium Entity / DataSource 高层 API
  • Cesium 屏幕空间拾取交互
  • GUI 参数调试面板

效果说明

本案例演示计算新坐标效果:基于 WebGL 实现「计算新坐标」可视化效果,附完整可运行源码;核心用到 Cesium。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • Viewer封装地球、相机、图层与 clock;可关闭 animation/timeline 精简 UI。
  • Entity适合业务对象;GeoJsonDataSource加载 GeoJSON 面线点。
  • ScreenSpaceEventHandler监听点击;scene.pick取 Entity,pickPosition取地表坐标。
  • dat.GUI / lil-gui 绑定 uniform 或配置对象实时调参。

实现步骤

  • 创建 Viewer,配置地形/影像(若案例需要)并设置初始相机
  • requestAnimationFrame循环中更新状态并 render(Cesium 为viewer.render或自动渲染)
  • 代码要点

    /**
    • Cesium点位计算与绘制工具
    • 本工具提供交互式点位绘制功能,可以根据起始点、方位角和距离计算新点位坐标并绘制在地图上
    • 使用球面三角学算法保证计算精度
    • * 功能说明:
    • 1. 用户点击"绘制原始点"按钮开始交互式绘制
    • 2. 在地图上点击选择起始点
    • 3. 输入方位角和距离参数
    • 4. 系统自动计算并绘制终点
    • 5. 调整视角以完整显示两个点
    */

    // ==================== 导入模块和初始化 ==================== import * as Cesium from 'cesium' import { GUI } from 'dat.gui'

    // 获取地图容器元素 const box = document.getElementById('box')

    // ==================== GUI控制面板 ==================== /**

    • 定义图形绘制操作对象
    • @namespace obj
    */ const obj = { /**
    • 绘制原始点功能 - 启动交互式绘制模式
    • @function
    • @memberof obj
    */ '绘制原始点': () => { setupInteractiveDrawing() }, };

    // 创建GUI控制面板并添加操作按钮 const gui = new GUI(); for (const key in obj) gui.add(obj, key)

    // ==================== Cesium Viewer初始化 ==================== /**

    • 初始化Cesium Viewer实例
    • @type {Cesium.Viewer}
    */ const viewer = new Cesium.Viewer(box, { animation: false, // 不显示动画控件 baseLayerPicker: false, // 不显示图层选择器 baseLayer: Cesium.ImageryLayer.fromProviderAsync( Cesium.ArcGisMapServerImageryProvider.fromUrl('https://server.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer') ), // 设置基础影像图层 fullscreenButton: false, // 不显示全屏按钮 timeline: false, // 不显示时间线 infoBox: false, // 不显示信息框 })

    // 隐藏Cesium默认的Logo信息 viewer._cesiumWidget._creditContainer.style.display = "none"; // 添加瓦片坐标信息 viewer.imageryLayers.addImageryProvider(new Cesium.TileCoordinatesImageryProvider()); // ==================== 状态管理 ==================== /**

    • 存储已创建的实体对象,用于后续操作和清理
    • @type {Array }
    */ let entitys = [];

    /**

    • 存储用户输入的距离值(米),用于视角调整
    • @type {number}
    */ let distance;

    /**

    • 全局事件处理器引用,用于管理交互事件
    • @type {Cesium.ScreenSpaceEventHandler}
    */ let globalHandler = null;

    // ==================== 交互功能 ==================== /**

    • 设置交互式绘制模式
    • 用户点击地图时绘制点,并请求输入方位角和距离以计算新点
    • 遵循事件管理规范,确保不会重复注册事件
    */ function setupInteractiveDrawing() { viewer.entities.removeAll(); entitys = []; // 清空实体数组 // 遵循事件管理规范:在注册新事件前清除旧事件避免重复注册 if (globalHandler) { globalHandler.destroy(); globalHandler = null; } // 创建屏幕空间事件处理器 globalHandler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

    // 注册鼠标左键点击事件 globalHandler.setInputAction(function (movement) { // 获取点击位置的射线 const ray = viewer.camera.getPickRay(movement.position); // 在地球上拾取点击位置 const cartesian = viewer.scene.globe.pick(ray, viewer.scene);

    if (cartesian) { // 将笛卡尔坐标转换为地理坐标(经纬度) const cartographic = Cesium.Cartographic.fromCartesian(cartesian); const lon = Cesium.Math.toDegrees(cartographic.longitude); const lat = Cesium.Math.toDegrees(cartographic.latitude);

    // 绘制第一个点(起始点) drawPointOnMap([lon, lat], '起点');

    // 遵循事件管理规范:完成一次绘制后清除事件处理器 if (globalHandler) { globalHandler.destroy(); globalHandler = null; } } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); }

    /**

    • 在地图上绘制点
    • @param {Array } coordinates - 坐标 [lon, lat]
    • @param {string} name - 点的名称(起点/终点)
    */ function drawPointOnMap(coordinates, name) { // 创建点实体并添加到Viewer中 const pointEntity = viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(coordinates[0], coordinates[1]), point: { pixelSize: 15, // 点的像素大小 color: Cesium.Color.RED, // 点的颜色 outlineColor: Cesium.Color.WHITE, // 点的边框颜色 outlineWidth: 3 // 点的边框宽度 }, label: { // 根据点的索引设置标签文本 text:${name}\n[${coordinates[0].toFixed(4)}, ${coordinates[1].toFixed(4)}], font: '16px sans-serif', // 字体样式 fillColor: Cesium.Color.WHITE, // 字体颜色 outlineColor: Cesium.Color.BLACK, // 字体边框颜色 outlineWidth: 2, // 字体边框宽度 style: Cesium.LabelStyle.FILL_AND_OUTLINE, // 标签样式 verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 垂直对齐方式 pixelOffset: new Cesium.Cartesian2(0, -15) // 像素偏移量 } });

    // 将创建的实体添加到实体数组中,便于后续管理 entitys.push(pointEntity);

    // 根据点的类型执行不同操作 if (name == '起点') { // 如果是起点,请求用户输入方位角和距离 requestAngleAndDistance(coordinates[0], coordinates[1]); } else { // 如果是终点,调整视角以完整显示所有点 viewer.flyTo(entitys, { duration: 1, offset: { heading: Cesium.Math.toRadians(0), pitch: Cesium.Math.toRadians(-90), range: distance * 1.5, // 根据距离调整视角范围 }, }); } }

    /**

    • 弹出输入框请求方位角和距离
    • 使用setTimeout确保在事件处理完成后才弹出对话框
    • @param {number} lon - 起始点经度
    • @param {number} lat - 起始点纬度
    */ function requestAngleAndDistance(lon, lat) { setTimeout(() => { // 弹出提示框请求用户输入 const value = prompt(请输入方位角&距离(km/>0)\n45&10 表示从正北方向顺时针45度方向,距离10公里, "45&10" );

    // 如果用户输入了值 if (value) { // 按"-"分割输入值 const splitValue = value.split("&");

    // 检查分割后的数组长度是否为2 if (splitValue.length == 2) { // 解析方位角和距离 const angle = parseFloat(splitValue[0]); distance = parseFloat(splitValue[1]) * 1000; // 转换为米

    // 检查解析后的值是否为有效数字 if (!isNaN(angle) && distance > 0) { // 计算新点坐标 const newCoordinates = newPointByAngleAndDistance(lon, lat, distance, angle); // 绘制新点 drawPointOnMap(newCoordinates, '终点'); } else { alert("输入格式不正确,请输入有效的数字"); viewer.entities.removeAll(); entitys = []; // 清空实体数组 } } else { alert("输入格式不正确,请按照格式:方位角&距离"); viewer.entities.removeAll(); entitys = []; // 清空实体数组 } } }, 500); }

    /**

    • 根据起始点、距离和角度计算新坐标点位置(方法1)
    • 使用球面三角学的公式计算,更精确但计算复杂度较高
    • * 该方法基于球面三角学公式,考虑地球曲率的影响,适用于中短距离的坐标计算
    • 采用椭球体模型,通过线性插值计算不同纬度的地球半径
    • * 球面三角学原理:
    • 在球面上,给定一个起始点和移动方向(方位角)及距离,可以计算出终点坐标
    • 使用了球面三角学中的正弦定律和余弦定律
    • * @param {number} lon - 起始点经度(度)
    • @param {number} lat - 起始点纬度(度)
    • @param {number} distance - 距离(米)
    • @param {number} angle - 方位角(度),正北为0度,顺时针增加
    • @returns {Array } [经度, 纬度] - 新点的经纬度坐标
    */ function newPointByAngleAndDistance(lon, lat, distance, angle) { // 将角度转换为弧度,因为三角函数需要弧度值 let angleRad = (angle * Math.PI) / 180;

    // 根据纬度计算地球半径(地球是椭球体) // 赤道半径6378137米,极地半径6356752米 // 纬度越高,半径越小,这里使用线性插值近似计算 let R = 6378137 - ((6378137 - 6356752) * lat) / 90;

    // 将起始点的经纬度转换为弧度 let latRad = (lat * Math.PI) / 180; let lonRad = (lon * Math.PI) / 180;

    // 使用球面三角学公式计算新点的纬度 // 这是球面三角学中的正弦定律应用: // sin(newLat) = sin(latRad)cos(distance/R) + cos(latRad)sin(distance/R) * cos(angleRad) // // 原理解释: // 1. 我们在球面上从起始点(latRad, lonRad)出发,沿着方位角angleRad方向移动distance距离 // 2. distance/R 是在球面上移动的弧度(因为弧长=半径×弧度) // 3. 这个公式考虑了: // - 起始点纬度的影响: sin(latRad) * cos(distance/R) // - 方位角的影响: cos(latRad)sin(distance/R)cos(angleRad) // 4. 最终通过反正弦函数(asin)得到新点的纬度弧度值 let newLat = Math.asin( Math.sin(latRad) * Math.cos(distance / R) + // 起始点纬度分量:保持原有纬度的部分影响 Math.cos(latRad)Math.sin(distance / R)Math.cos(angleRad) // 方位角影响分量:方位角和移动距离共同决定的纬度变化 );

    // 计算新点的经度 // 使用球面三角学中的公式计算经度变化 // 这里使用atan2函数确保结果在正确的象限内 // // 原理解释: // 1. atan2(y, x)函数用于计算从x轴到点(x,y)的弧度角,结果范围是[-π, π] // 2. 分子部分:Math.sin(angleRad)Math.sin(distance / R)Math.cos(latRad) // 表示东西向(经度方向)的变化分量 // 3. 分母部分:Math.cos(distance / R) - Math.sin(latRad) * Math.sin(newLat) // 表示南北向(纬度方向)的变化分量 // 4. 将这个角度变化量加到原始经度上,得到新点的经度 let newLon = lonRad + Math.atan2( Math.sin(angleRad)Math.sin(distance / R)Math.cos(latRad), // 东西向分量:方位角和距离在经度方向的投影 Math.cos(distance / R) - Math.sin(latRad) * Math.sin(newLat) // 南北向分量:考虑起始点和终点纬度影响的修正项 );

    // 将计算出的弧度转换回角度 newLat = (newLat * 180) / Math.PI; newLon = (newLon * 180) / Math.PI;

    return [newLon, newLat]; }

    完整源码:GitHub

    小结

    • 本文提供计算新坐标完整 Cesium.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Cesium.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库