本文主要实现基础的绘制圆形,并且可以通过拖动圆心更新圆的位置,拖动圆上的边缘点改变圆的半径。
实现效果:
(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 坐标
}