返回 导航

Vue.js

hangge.com

Vue.js - 集成Cesium构建三维可视化场景教程3(获取点击处的经纬度、实体位置)

作者:hangge | 2026-07-04 09:37
    我在前文演示了如何实现 CesiumVue 项目中的基础集成。在实际项目中,常常需要实现点击地图获取当前位置经纬度与高度功能,并且能识别用户点击的是地形、模型还是实体。下面我将它通过样例演示如何获取鼠标点击处的经纬度、高度,以及实体(Entity)的位置坐标。

三、获取鼠标点击处的经纬度、高度、实体位置

1,Cesium 中的坐标体系

(1)世界坐标(Cartesian3
  • Cesium 内部统一使用的三维笛卡尔坐标
  • 单位:米
  • 原点:地球中心
  • Cesium 中,Cartesian3 类表示三维笛卡尔坐标系中的一个点,它由三个分量 (xyz) 组成,分别表示点在 xyz 轴上的位置。
  • 通过使用 Cartesian3 类,我们可以在 Cesium 中表示和操作三维空间中的点,例如表示摄像机位置、实体位置、矢量等。Cartesian3 类提供了许多方法和属性,可以帮助我们进行点的计算、转换和操作
(2)地理坐标(经纬度)
  • 这个是我们最常用的坐标形式。
  • Cesium 中,Cartographic 类表示地理坐标。其内部包含:经度(longitude)、纬度(latitude)、高程(height)。
(3)屏幕坐标(Window Position
  • 也就是鼠标点击位置。屏幕左上角为原点,屏幕水平方向为 X 轴,垂直方向为 Y 轴,向下为正。
  • 单位:像素
new Cesium.Cartesian2(x, y)

2,样例代码

(1)下面样例监听鼠标点击事件,通过“屏幕坐标 → 世界坐标 → 经纬度坐标”这个流程获取鼠标点击处的经纬度,同时区分点击的是地形、模型还是实体。
<template>
    <div class="layer-container">
        <div ref="cesiumContainer" class="cesium-container"></div>

        <!-- 加载状态 -->
        <div v-if="loading" class="loading-overlay">
            <div class="loading-spinner"></div>
            <p>加载三维场景中...</p>
        </div>
    </div>
</template>

<script>
// 引入 Cesium 核心类
import * as Cesium from "cesium";

export default {
    name: 'CesiumViewer',

    data() {
        return {
            viewer: null,
            loading: true,
            terrainEnabled: true,
            sampleEntities: [],
            clickHandler: null
        }
    },

    mounted() {
        // 确保 Cesium 已加载
        if (typeof Cesium === 'undefined') {
            console.error('Cesium 未加载,请检查配置')
            this.loading = false
            return
        }
        // 初始化 Cesium 场景
        this.initCesium()
    },

    beforeDestroy() {
        // 组件销毁时清理资源
        this.destroyCesium()
    },

    methods: {
        // 初始化 Cesium
        async initCesium() {
            try {
                // 配置 Cesium 访问令牌(如果需要,从 Cesium Ion 获取)
                //Cesium.Ion.defaultAccessToken = '你的Cesium Ion访问令牌'

                // 创建 Viewer 实例
                this.viewer = new Cesium.Viewer(this.$refs.cesiumContainer, {
                    animation: false, // 是否显示动画控件
                    baseLayerPicker: false, // 是否显示图层选择控件
                    fullscreenButton: false, // 是否显示全屏按钮
                    geocoder: false, // 是否显示地理编码搜索控件
                    homeButton: false, // 是否显示Home按钮
                    infoBox: false, // 是否显示信息框
                    sceneModePicker: false, // 是否显示3D/2D选择器
                    selectionIndicator: false, // 是否显示选取指示器组件
                    timeline: false, // 是否显示时间轴
                    navigationHelpButton: false, // 是否显示帮助按钮
                    scene3DOnly: true, // 每个几何实例仅以3D渲染以节省GPU内存
                    shouldAnimate: true, // 自动播放动画
                    // 地形配置
                    terrainProvider: await Cesium.createWorldTerrainAsync()
                })

                // 强制重新计算画布尺寸
                this.viewer.canvas.width = this.viewer.container.clientWidth;
                this.viewer.canvas.height = this.viewer.container.clientHeight;
                this.viewer.resize(); // 核心方法:告知 Cesium 容器大小已变

                // 设置初始视图
                this.setInitialView()

                // 添加点
                this.addPointEntity()

                // 监听鼠标点击事件
                this.clickHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
                this.clickHandler.setInputAction(this.onClick,
                    Cesium.ScreenSpaceEventType.LEFT_CLICK)

                // 加载完成
                this.loading = false

                console.log('Cesium 场景初始化完成')
            } catch (error) {
                console.error('初始化 Cesium 失败:', error)
                this.loading = false
            }
        },

        // 鼠标点击事件响应
        onClick(event) {
            const scene = this.viewer.scene
            const globe = scene.globe

            // 1,获取点击位置的射线
            const ray = this.viewer.camera.getPickRay(event.position)
            if (!ray) return

            // 2,与地球 / 地形求交
            const cartesian = globe.pick(ray, scene)
            if (!cartesian) return

            // 3,笛卡尔坐标 → 经纬度
            const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
            const lon = Cesium.Math.toDegrees(cartographic.longitude)
            const lat = Cesium.Math.toDegrees(cartographic.latitude)
            const height = cartographic.height

            console.log('经度:', lon)
            console.log('纬度:', lat)
            console.log('高度:', height)

            // 4,判断是否点中了实体
            const picked = scene.pick(event.position)
            if (Cesium.defined(picked) && picked.id) {
                const entity = picked.id
                const cartesian = entity.position.getValue(this.viewer.clock.currentTime)
                const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
                console.log('点击实体:', entity)
                console.log('实体经度:', Cesium.Math.toDegrees(cartographic.longitude))
                console.log('实体纬度:', Cesium.Math.toDegrees(cartographic.latitude))
            }
        },

        // 设置初始视图
        setInitialView() {
            // 设置初始位置(例如:北京天安门)
            const initialPosition = Cesium.Cartesian3.fromDegrees(116.3914, 39.9074, 1100)
            this.viewer.camera.setView({
                destination: initialPosition,
                orientation: {
                    heading: Cesium.Math.toRadians(0),
                    pitch: Cesium.Math.toRadians(-90),
                    roll: 0
                }
            })
        },

        // 添加点
        addPointEntity() {
            const beijingPoint = this.viewer.entities.add({
                name: '北京天安门',
                position: Cesium.Cartesian3.fromDegrees(116.3914, 39.9074),
                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, -20),
                    disableDepthTestDistance: Number.POSITIVE_INFINITY // 关闭深度测试,防止被遮挡
                }
            })

            this.sampleEntities.push(beijingPoint)
        },

        // 销毁 Cesium 资源
        destroyCesium() {
            if (this.clickHandler) {
                this.clickHandler.destroy()
                this.clickHandler = null
            }
            if (this.viewer) {
                this.viewer.destroy()
                this.viewer = null
            }
        }
    }
}
</script>

<style scoped>
.layer-container {
    position: relative;
    width: 100%;
    height: 100%;
}

.cesium-container {
    width: 100%;
    height: 100%;
}

.loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 1000;
    color: white;
}

.loading-spinner {
    border: 4px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top: 4px solid #42b983;
    width: 50px;
    height: 50px;
    animation: spin 1s linear infinite;
    margin-bottom: 15px;
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}
</style>

(2)运行效果如下,当我们点击地图上任意一点时控制台会显示该位置的经度、维度、高度。

(3)如果点击实体时,除了会显示点击位置的经度、维度、高度外,还会获取实体位置的经纬度并打印出来。
评论

全部评论(0)

回到顶部