本文主要实现基础的绘制圆形,并且可以通过拖动圆心更新圆的位置,拖动圆上的边缘点改变圆的半径。
实现效果:
(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.idconst 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) // cartesian3if (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 = nullif (isDrawing) {endPosition = window.viewer.camera.pickEllipsoid(event.position, window.viewer.scene.globe.ellipsoid) // 保存圆的边缘点}isDrawing = falsewindow.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 = truewindow.viewer.scene.screenSpaceCameraController.enableTranslate = truewindow.viewer.scene.screenSpaceCameraController.enableZoom = truewindow.viewer.scene.screenSpaceCameraController.enableTilt = truewindow.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 坐标
}