Cesium中实现在地图上移动/旋转点、线、面

一、上/下/左/右整体移动

复制代码
<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cesium 点线面整体平移</title>
    <!-- 引入Cesium CDN -->
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.108/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.108/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        #cesiumContainer {
            width: 100vw;
            height: 100vh;
            position: relative;
        }

        /* 平移按钮样式 */
        .move-controls {
            position: absolute;
            top: 20px;
            left: 20px;
            z-index: 100;
            display: grid;
            grid-template-columns: repeat(3, 40px);
            grid-template-rows: repeat(3, 40px);
            gap: 5px;
        }

        .move-btn {
            border: none;
            background: rgba(0, 123, 255, 0.8);
            color: white;
            font-size: 16px;
            border-radius: 4px;
            cursor: pointer;
            transition: background 0.3s;
        }

        .move-btn:hover {
            background: rgba(0, 86, 179, 0.8);
        }

        /* 按钮布局:上、下、左、右居中 */
        .btn-up {
            grid-area: 1 / 2 / 2 / 3;
        }

        .btn-down {
            grid-area: 3 / 2 / 4 / 3;
        }

        .btn-left {
            grid-area: 2 / 1 / 3 / 2;
        }

        .btn-right {
            grid-area: 2 / 3 / 3 / 4;
        }
    </style>
</head>

<body>
    <div id="cesiumContainer">
        <!-- 平移控制按钮 -->
        <div class="move-controls">
            <button class="move-btn btn-up">↑</button>
            <button class="move-btn btn-down">↓</button>
            <button class="move-btn btn-left">←</button>
            <button class="move-btn btn-right">→</button>
        </div>
    </div>

    <script>
        // 1. 初始化Cesium场景
        Cesium.Ion.defaultAccessToken = 'Access Token'

        const viewer = new Cesium.Viewer("cesiumContainer", {
            // terrainProvider: Cesium.createWorldTerrain(),
            timeline: false,
            animation: false
        });
        viewer.camera.setView({
            destination: Cesium.Cartesian3.fromDegrees(116.403963, 39.915112, 1000) // 聚焦北京天安门附近
        });

        // 2. 定义平移增量(每次点击移动的距离,单位:度(经纬度)、米(高度))
        // 注:1度纬度≈111公里,此处设置0.001度≈111米,平移效果更直观
        const MOVE_OFFSET = {
            lon: 0.001,  // 经度增量(左右移动)
            lat: 0.001,  // 纬度增量(上下移动)
            height: 0    // 高度增量(可按需调整,实现垂直升降)
        };

        // 3. 创建测试实体(点、线、面)
        let testEntities = [];

        // 3.1 创建点实体
        const pointEntity = viewer.entities.add({
            name: "测试点",
            position: Cesium.Cartesian3.fromDegrees(116.403963, 39.915112, 100),
            point: {
                pixelSize: 10,
                color: Cesium.Color.RED,
                outlineColor: Cesium.Color.WHITE,
                outlineWidth: 2
            }
        });

        // 3.2 创建线实体
        const polylineEntity = viewer.entities.add({
            name: "测试线",
            polyline: {
                positions: Cesium.Cartesian3.fromDegreesArray([
                    116.402963, 39.914112,
                    116.404963, 39.915112,
                    116.403963, 39.916112
                ]),
                width: 5,
                material: Cesium.Color.BLUE,
                clampToGround: false
            }
        });

        // 3.3 创建面实体
        const polygonEntity = viewer.entities.add({
            name: "测试面",
            polygon: {
                hierarchy: Cesium.Cartesian3.fromDegreesArray([
                    116.400963, 39.913112,
                    116.405963, 39.913112,
                    116.405963, 39.917112,
                    116.400963, 39.917112
                ]),
                material: Cesium.Color.GREEN.withAlpha(0.5),
                outline: true,
                outlineColor: Cesium.Color.DARK_GREEN,
                outlineWidth: 2,
                clampToGround: false
            }
        });

        // 收集所有测试实体
        testEntities = [pointEntity, polylineEntity, polygonEntity];

        // 4. 核心:平移实体的通用方法
        /**
         * 平移Cesium实体(支持点、线、面)
         * @param {Cesium.Entity} entity - 要平移的实体
         * @param {number} lonOffset - 经度偏移量(正值右移,负值左移)
         * @param {number} latOffset - 纬度偏移量(正值上移,负值下移)
         * @param {number} heightOffset - 高度偏移量(正值上升,负值下降)
         */
        function translateEntity(entity, lonOffset, latOffset, heightOffset) {
            // 情况1:点实体(拥有position属性)
            if (entity.position) {
                // 获取当前笛卡尔坐标
                const currentCartesian = entity.position.getValue(Cesium.JulianDate.now());
                if (!currentCartesian) return;

                // 转换为地理坐标(经纬度+高度)
                const currentCartographic = Cesium.Cartographic.fromCartesian(currentCartesian);

                // 计算新的地理坐标
                const newLon = currentCartographic.longitude + Cesium.Math.toRadians(lonOffset);
                const newLat = currentCartographic.latitude + Cesium.Math.toRadians(latOffset);
                const newHeight = currentCartographic.height + heightOffset;

                // 转换回笛卡尔坐标并更新实体位置
                const newCartesian = Cesium.Cartesian3.fromRadians(newLon, newLat, newHeight);
                entity.position = newCartesian;
            }

            // 情况2:线/面实体(拥有polyline.positions 或 polygon.hierarchy)
            // 提取所有需要平移的坐标点
            let positions = null;
            if (entity.polyline && entity.polyline.positions) {
                positions = entity.polyline.positions.getValue(Cesium.JulianDate.now());
            } else if (entity.polygon && entity.polygon.hierarchy) {
                const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
                positions = hierarchy ? hierarchy.positions : null;
            }

            if (positions && positions.length > 0) {
                const newPositions = [];
                for (let i = 0; i < positions.length; i++) {
                    // 单个坐标点的平移逻辑(与点实体一致)
                    const currentCartographic = Cesium.Cartographic.fromCartesian(positions[i]);
                    const newLon = currentCartographic.longitude + Cesium.Math.toRadians(lonOffset);
                    const newLat = currentCartographic.latitude + Cesium.Math.toRadians(latOffset);
                    const newHeight = currentCartographic.height + heightOffset;
                    const newCartesian = Cesium.Cartesian3.fromRadians(newLon, newLat, newHeight);
                    newPositions.push(newCartesian);
                }

                // 更新线/面的坐标集合
                if (entity.polyline) {
                    entity.polyline.positions = newPositions;
                } else if (entity.polygon) {
                    entity.polygon.hierarchy = new Cesium.PolygonHierarchy(newPositions);
                }
            }
        }

        // 5. 绑定按钮点击事件,实现上下左右平移
        document.querySelector(".btn-up").addEventListener("click", () => {
            // 上移:纬度增加(北纬越大,位置越北)
            testEntities.forEach(entity => {
                translateEntity(entity, 0, MOVE_OFFSET.lat, MOVE_OFFSET.height);
            });
        });

        document.querySelector(".btn-down").addEventListener("click", () => {
            // 下移:纬度减少
            testEntities.forEach(entity => {
                translateEntity(entity, 0, -MOVE_OFFSET.lat, MOVE_OFFSET.height);
            });
        });

        document.querySelector(".btn-left").addEventListener("click", () => {
            // 左移:经度减少(东经越大,位置越东,左移即经度减小)
            testEntities.forEach(entity => {
                translateEntity(entity, -MOVE_OFFSET.lon, 0, MOVE_OFFSET.height);
            });
        });

        document.querySelector(".btn-right").addEventListener("click", () => {
            // 右移:经度增加
            testEntities.forEach(entity => {
                translateEntity(entity, MOVE_OFFSET.lon, 0, MOVE_OFFSET.height);
            });
        });
    </script>
</body>

</html>

核心方法解析(translateEntity):

该方法是实现平移的核心,兼容点、线、面三种实体类型,核心流程为:获取当前Cartesian3坐标 → 转换为Cartographic地理坐标 → 叠加平移增量 → 转换回Cartesian3坐标 → 更新实体位置。

线 / 面实体的处理逻辑是遍历其所有顶点坐标,对每个顶点执行相同的平移操作,从而实现整体移动,保证形状不发生变化。

平移增量调整:

MOVE_OFFSET中定义了经纬度和高度的增量,可根据需求调整:

  • 增大lon/lat可提高平移速度,减小则更精细。
  • 调整height可实现实体的垂直升降(如设置为 10,每次点击上升 10 米)。

二、旋转

保持形状、大小不变

复制代码
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
    <link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>

<body>
    <div id="cesiumContainer"></div>
    <div style="position: absolute; top: 10px; left: 10px; z-index: 1000;">
        <button id="clockwiseBtn">顺时针旋转</button>
        <button id="counterclockwiseBtn">逆时针旋转</button>
        <input type="number" id="rotationAngle" value="15" min="1" max="90">
        <label for="rotationAngle">旋转角度(度)</label>
    </div>

    <script>
        Cesium.Ion.defaultAccessToken = 'Access Token'

        // 初始化Cesium Viewer
        const viewer = new Cesium.Viewer('cesiumContainer', {
            // terrainProvider: Cesium.createWorldTerrain(),
            baseLayerPicker: false,
            animation: false,
            timeline: false
        });

        // 存储原始图形数据
        let originalEntities = [];
        let isOriginalDataSaved = false;

        // 创建示例图形(点、线、面)
        function createExampleEntities() {
            // 清除现有实体
            viewer.entities.removeAll();
            originalEntities = [];

            // 示例多边形顶点(三角形)
            const polygonPositions = Cesium.Cartesian3.fromDegreesArray([
                -115.0, 37.0,
                -115.0, 32.0,
                -107.0, 33.0
            ]);

            // 创建面(多边形)
            const polygonEntity = viewer.entities.add({
                polygon: {
                    hierarchy: polygonPositions,
                    material: Cesium.Color.GREEN.withAlpha(0.5),
                    outline: true,
                    outlineColor: Cesium.Color.GREEN
                }
            });

            // 创建线(多段线)
            const polylineEntity = viewer.entities.add({
                polyline: {
                    positions: polygonPositions,
                    width: 3,
                    material: Cesium.Color.BLUE
                }
            });

            // 创建点(在顶点位置)
            const pointEntities = polygonPositions.map((position, index) => {
                return viewer.entities.add({
                    position: position,
                    point: {
                        pixelSize: 10,
                        color: Cesium.Color.RED
                    },
                    label: {
                        text: `点 ${index + 1}`,
                        font: '14px sans-serif',
                        pixelOffset: new Cesium.Cartesian2(0, 20)
                    }
                });
            });

            // 保存原始数据
            originalEntities.push({
                type: 'polygon',
                positions: polygonPositions
            });
            originalEntities.push({
                type: 'polyline',
                positions: polygonPositions
            });
            pointEntities.forEach((entity, index) => {
                originalEntities.push({
                    type: 'point',
                    position: polygonPositions[index],
                    entity: entity
                });
            });

            // 缩放到图形范围
            viewer.zoomTo(viewer.entities);

            isOriginalDataSaved = true;
        }

        // 计算图形的几何中心
        function calculateGeometricCenter(positions) {
            const cartographics = positions.map(cartesian =>
                Cesium.Cartographic.fromCartesian(cartesian)
            );

            // 计算平均经纬度和高度
            const avgLat = cartographics.reduce((sum, carto) => sum + carto.latitude, 0) / cartographics.length;
            const avgLon = cartographics.reduce((sum, carto) => sum + carto.longitude, 0) / cartographics.length;
            const avgHeight = cartographics.reduce((sum, carto) => sum + carto.height, 0) / cartographics.length;

            return Cesium.Cartesian3.fromRadians(avgLon, avgLat, avgHeight);
        }

        // 旋转函数
        function rotateEntities(angleDegrees, clockwise = true) {
            if (!isOriginalDataSaved) {
                console.log("请先创建图形");
                return;
            }

            const angle = clockwise ? -angleDegrees : angleDegrees;
            const angleRadians = Cesium.Math.toRadians(angle);

            // 计算所有原始顶点的中心点
            const allPositions = originalEntities
                .filter(entity => entity.positions || entity.position)
                .flatMap(entity => entity.positions ? entity.positions : [entity.position]);

            const center = calculateGeometricCenter(allPositions);

            // 获取当前所有实体
            const entities = viewer.entities.values;

            entities.forEach(entity => {
                if (entity.polygon) {
                    // 旋转多边形
                    const polygonPositions = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
                    const rotatedPositions = rotatePositions(polygonPositions.positions, center, angleRadians);
                    entity.polygon.hierarchy = rotatedPositions;
                }

                if (entity.polyline) {
                    // 旋转线
                    const polylinePositions = entity.polyline.positions.getValue(Cesium.JulianDate.now());
                    const rotatedPositions = rotatePositions(polylinePositions, center, angleRadians);
                    entity.polyline.positions = rotatedPositions;
                }

                if (entity.point) {
                    // 旋转点
                    const position = entity.position.getValue(Cesium.JulianDate.now());
                    const rotatedPosition = rotatePosition(position, center, angleRadians);
                    entity.position = rotatedPosition;
                }
            });
        }

        // 旋转单个点
        function rotatePosition(position, center, angleRadians) {
            // 将笛卡尔坐标转换为经纬度
            const cartographic = Cesium.Cartographic.fromCartesian(position);
            const centerCartographic = Cesium.Cartographic.fromCartesian(center);

            // 计算相对中心点的经纬度差
            const deltaLon = cartographic.longitude - centerCartographic.longitude;
            const deltaLat = cartographic.latitude - centerCartographic.latitude;

            // 使用平面近似(在小范围内有效)
            const x = deltaLon * Math.cos(centerCartographic.latitude);
            const y = deltaLat;

            // 应用2D旋转矩阵
            const cosA = Math.cos(angleRadians);
            const sinA = Math.sin(angleRadians);

            const xRotated = x * cosA - y * sinA;
            const yRotated = x * sinA + y * cosA;

            // 转换回经纬度
            const rotatedLon = centerCartographic.longitude + xRotated / Math.cos(centerCartographic.latitude);
            const rotatedLat = centerCartographic.latitude + yRotated;

            return Cesium.Cartesian3.fromRadians(rotatedLon, rotatedLat, cartographic.height);
        }

        // 旋转位置数组
        function rotatePositions(positions, center, angleRadians) {
            const rotatedPositions = [];
            for (let i = 0; i < positions.length; i++) {
                rotatedPositions.push(rotatePosition(positions[i], center, angleRadians));
            }
            return rotatedPositions;
        }

        // 重置到原始状态
        function resetToOriginal() {
            if (!isOriginalDataSaved) return;

            viewer.entities.removeAll();

            // 使用原始数据重新创建实体
            originalEntities.forEach(original => {
                if (original.type === 'polygon') {
                    viewer.entities.add({
                        polygon: {
                            hierarchy: original.positions,
                            material: Cesium.Color.GREEN.withAlpha(0.5),
                            outline: true,
                            outlineColor: Cesium.Color.GREEN
                        }
                    });
                } else if (original.type === 'polyline') {
                    viewer.entities.add({
                        polyline: {
                            positions: original.positions,
                            width: 3,
                            material: Cesium.Color.BLUE
                        }
                    });
                }
                // 点会在后续步骤中重新创建
            });
        }

        // 事件监听
        document.getElementById('clockwiseBtn').addEventListener('click', () => {
            const angle = parseFloat(document.getElementById('rotationAngle').value);
            rotateEntities(angle, true);
        });

        document.getElementById('counterclockwiseBtn').addEventListener('click', () => {
            const angle = parseFloat(document.getElementById('rotationAngle').value);
            rotateEntities(angle, false);
        });

        // 添加重置按钮(可选)
        const resetBtn = document.createElement('button');
        resetBtn.textContent = '重置图形';
        resetBtn.style.marginLeft = '10px';
        resetBtn.addEventListener('click', resetToOriginal);
        document.querySelector('div[style]').appendChild(resetBtn);

        // 初始化创建示例图形
        createExampleEntities();
    </script>
</body>

</html>

关键特性

统一旋转中心:计算所有几何图形的几何中心作为旋转中心

保持形状大小:通过保持各点到中心的距离不变来实现

支持多种图形:同时处理点、线、面

可调旋转角度:可以通过输入框设置旋转角度

双向旋转:支持顺时针和逆时针旋转

注意事项

这个实现在小范围内(几十公里)效果较好

对于大范围的图形,需要考虑地球曲率的影响

旋转操作会修改原始图形数据,可以通过重置功能恢复

相关推荐
duansamve3 天前
Cesium 线段分割和删除
cesium
YAY_tyy4 天前
Cesium 基础:地球场景初始化与视角控制
前端·cesium
ct97811 天前
Cesium中的CZML
学习·gis·cesium
weipt13 天前
关于vue项目中cesium的地图显示问题
前端·javascript·vue.js·cesium·卫星影像·地形
YAY_tyy14 天前
综合实战:基于 Turfjs 的智慧园区空间管理系统
前端·3d·cesium·turfjs
haokan_Jia14 天前
【三、基于Cesium的三维智慧流域四预系统-轻量级搭建】
cesium
YAY_tyy14 天前
Turfjs 性能优化:大数据量地理要素处理技巧
前端·3d·arcgis·cesium·turfjs
Tiam-201614 天前
cesium使用cesium-plot-js标绘多种图形
javascript·vue.js·arcgis·es6·gis·cesium·cesium-plot-js
gis_rc20 天前
python下shp转3dtiles
python·3d·cesium·3dtiles·数字孪生模型
grasperp21 天前
3DTiles数据切片工具,支持LAS、OBJ、FBX 3DTiles怎么切片?3DTiles切片
cesium·3dtiles·三维gis·3dtiles切片·数据切片