vue3 + ts + cesium:绘制、更新圆 ellipse

本文主要实现基础的绘制圆形,并且可以通过拖动圆心更新圆的位置,拖动圆上的边缘点改变圆的半径。

实现效果:

    (1)单击鼠标左键开始绘制,确定圆的圆心,移动鼠标,改变圆的半径;单击鼠标右键,结束绘制。

    (2)鼠标左键单击绘制的圆形,显示圆的圆心和边缘点;长按鼠标左键,拖动圆心,实时更新圆的位置;长按鼠标左键,拖动边缘点,实时更新圆的半径;单击鼠标右键,结束更新操作,不再显示圆心和边缘点。

1. components / CesiumViewer / hooks / drawCircle.ts(绘制/更新代码)

import * as Cesium from "cesium";
import {CallbackProperty} from "cesium";
import {
    cartesian2ToCartesian3,
    disableDefaultScreenSpaceEventHandlers,
    enableDefaultScreenSpaceEventHandlers
} from "@/components/CesiumViewer/hooks/utils";

/* 绘制圆 */
export const drawCircle = () => {
    const handler = new Cesium.ScreenSpaceEventHandler(window.viewer.scene.canvas)
    const updateHandler = new Cesium.ScreenSpaceEventHandler(window.viewer.scene.canvas)
    let isDrawing = true // 是否处于绘制状态
    let centerPosition: any // 中心点的位置
    let centerPoint: any // 中心点
    let radius: any // 圆的半径
    let tempCircles: any[] = [] // 保存一次绘制过程中产生的圆
    let endPosition: any // 边缘点的位置
    let endPoint: any // 边缘点
    let pickedCircle: any // 选中的圆
    // 单击左键 —— 绘制圆 / 选中圆
    handler.setInputAction((event: any) => {
        const pickedObject = window.viewer.scene.pick(event.position) // 拾取实体
        if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.ellipse && !isDrawing) { // 选中圆
            pickedCircle = pickedObject.id
            const centerEntity: any = {
                // position: centerPosition,
                position: new Cesium.CallbackProperty(() => centerPosition, false),
                point: {
                    pixelSize: 20, // 点的大小
                    color: Cesium.Color.YELLOW,
                    /* 根据视角远近控制点的比例 */
                    scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 8.0e6, 0.0),
                    heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
                },
                type: 'center'
            }
            centerPoint = window.viewer.entities.add(centerEntity)
            const endEntity: any = {
                // position: endPosition,
                position: new CallbackProperty(() => endPosition, false),
                point: {
                    pixelSize: 20, // 点的大小
                    color: Cesium.Color.YELLOW,
                    /* 根据视角远近控制点的比例 */
                    scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 8.0e6, 0.0),
                    heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
                },
                type: 'end'
            }
            endPoint = window.viewer.entities.add(endEntity)
        } else { // 绘制圆
            if (isDrawing) {
                centerPosition = window.viewer.camera.pickEllipsoid(event.position, window.viewer.scene.globe.ellipsoid) // cartesian3
                if (Cesium.defined(centerPosition) && isDrawing) {
                    centerPoint = window.viewer.entities.add({
                        position: centerPosition,
                        point: {
                            pixelSize: 20, // 点的大小
                            color: Cesium.Color.YELLOW,
                            /* 根据视角远近控制点的比例 */
                            scaleByDistance: new Cesium.NearFarScalar(1.5e2, 2.0, 8.0e6, 0.0),
                            heightReference: Cesium.HeightReference.RELATIVE_TO_GROUND
                        }
                    })
                    handler.setInputAction((movement: any) => {
                        // 计算半径【movement.endPosition 为 cartesian2 坐标】
                        radius = Cesium.Cartesian3.distance(centerPosition, <Cesium.Cartesian3>cartesian2ToCartesian3(movement.endPosition))
                        const tempCircle = window.viewer.entities.add({
                            position: centerPosition, // 圆心位置
                            ellipse: {
                                semiMinorAxis: new Cesium.CallbackProperty(() => radius, false), // 短半轴
                                semiMajorAxis: new Cesium.CallbackProperty(() => radius, false), // 长半轴(设置为相等以形成圆形)
                                material: new Cesium.ColorMaterialProperty(Cesium.Color.RED.withAlpha(0.5)), // 圆形的填充颜色和透明度
                                /*// 圆环
                                outline: true, // 轮廓线
                                outlineColor: Cesium.Color.YELLOW, // 轮廓颜色
                                fill: false // 无填充*/
                            }
                        })
                        tempCircles.push(tempCircle)
                        if (tempCircles.length > 1) {
                            for (let i = 0; i < tempCircles.length - 1; i++) {
                                window.viewer.entities.remove(tempCircles[i]) // 实时更新半径时会绘制多个圆,并且堆叠在一起,所以需要保证只球上只渲染最新的圆
                            }
                        }
                    }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
                }
            }
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK)
    // 单击右键 —— 结束绘制
    handler.setInputAction((event: any) => {
        handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        tempCircles = []
        window.viewer.entities.remove(centerPoint) // 结束绘制 / 结束更新后 移除中心点
        centerPoint = null
        if (isDrawing) {
            endPosition = window.viewer.camera.pickEllipsoid(event.position, window.viewer.scene.globe.ellipsoid) // 保存圆的边缘点
        }
        isDrawing = false
        window.viewer.entities.remove(endPoint) // 结束更新后 移除边缘点
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
    // 长按左键 —— 更新绘制的圆圈
    handler.setInputAction((event: any) => {
        const pickedObject = window.viewer.scene.pick(event.position)
        if (Cesium.defined(pickedObject) && pickedObject.id && pickedObject.id.point) {
            disableDefaultScreenSpaceEventHandlers()
            updateHandler.setInputAction((movement: any) => {
                const newPosition = window.viewer.camera.pickEllipsoid(movement.endPosition, window.viewer.scene.globe.ellipsoid)
                if (Cesium.defined(newPosition)) {
                    pickedObject.id.position = new Cesium.CallbackProperty(() => newPosition, false) // 实时更新拖动的点的位置
                    if (pickedObject.id.type === 'center') { // 中心点 —— 拖动圆
                        pickedCircle.position = new Cesium.CallbackProperty(() => newPosition, false) // 更新圆心位置
                        centerPosition = newPosition // 更新中心点的位置
                        // 计算新的边缘点位置
                        let offsetDirection = Cesium.Cartesian3.subtract(endPosition, centerPosition, new Cesium.Cartesian3()) // 从中心点到边缘点的方向
                        let normalizedDirection = Cesium.Cartesian3.normalize(offsetDirection, new Cesium.Cartesian3()) // 单位方向向量
                        endPosition = Cesium.Cartesian3.add(centerPosition, Cesium.Cartesian3.multiplyByScalar(normalizedDirection, radius, new Cesium.Cartesian3()), new Cesium.Cartesian3())
                        endPoint.position = new Cesium.CallbackProperty(() => endPosition, false)
                    }
                    if (pickedObject.id.type === 'end') { // 边缘点 —— 改变圆的半径
                        radius = Cesium.Cartesian3.distance(centerPosition, <Cesium.Cartesian3>cartesian2ToCartesian3(movement.endPosition))
                        pickedCircle.ellipse.semiMinorAxis = new Cesium.CallbackProperty(() => radius, false)
                        pickedCircle.ellipse.semiMajorAxis = new Cesium.CallbackProperty(() => radius, false)
                        endPosition = newPosition
                    }
                }
            }, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        }
    }, Cesium.ScreenSpaceEventType.LEFT_DOWN)
    // 抬起左键 —— 结束更新
    handler.setInputAction(() => {
        updateHandler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
        enableDefaultScreenSpaceEventHandlers() // 恢复允许屏幕移动
    }, Cesium.ScreenSpaceEventType.LEFT_UP)
}

2. components / CesiumViewer / hooks / utils.ts (禁止/允许屏幕拖动、屏幕坐标转世界坐标代码)

import * as Cesium from "cesium";

// 保持地球不动
export function disableDefaultScreenSpaceEventHandlers() {
    window.viewer.scene.screenSpaceCameraController.enableRotate = false // 禁止旋转
    window.viewer.scene.screenSpaceCameraController.enableTranslate = false // 禁止平移
    window.viewer.scene.screenSpaceCameraController.enableZoom = false // 禁止缩放
    window.viewer.scene.screenSpaceCameraController.enableTilt = false // 禁止倾斜
    window.viewer.scene.screenSpaceCameraController.enableLook = false // 禁止观察(自由视角查看)
}

// 允许地球移动
export function enableDefaultScreenSpaceEventHandlers() {
    window.viewer.scene.screenSpaceCameraController.enableRotate = true
    window.viewer.scene.screenSpaceCameraController.enableTranslate = true
    window.viewer.scene.screenSpaceCameraController.enableZoom = true
    window.viewer.scene.screenSpaceCameraController.enableTilt = true
    window.viewer.scene.screenSpaceCameraController.enableLook = true
}

// 屏幕坐标转世界坐标(cartesian2 → cartesian3)
export function cartesian2ToCartesian3(cartesian2: Cesium.Cartesian2) {
    // 获取相机的射线
    const ray: any = window.viewer.camera.getPickRay(cartesian2)
    // 使用射线来获取地球表面上的位置
    return window.viewer.scene.globe.pick(ray, window.viewer.scene) // 返回 Cartesian3 坐标
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值