本学习系列将以Cesium + Vue3 + Vite +Typescript+elementplus作为主要技术栈展开,后续会循序渐进,持续探索Cesium的高级功能,详情请关注原文Cesium+Vue3学习教程系列(22)---一种更加实用的椭圆绘制与编辑方法
上篇文章Cesium+Vue3学习教程系列(21)---Cesium与turf.js结合,以椭圆形的绘制与编辑为例中使用的是两点法绘制椭圆,即通过矩形的角点确定椭圆的长半轴、短半轴及椭圆的圆心,再利用Turf.js库计算椭圆形的图形点,最后使用Cesium进行多边形的绘制。这种方式其实是有些缺陷的,那就是生成的椭圆形不经过控制点,形状不可控,而且都是正交的,无法绘制具有一定旋转角度的椭圆形。
本文给出一种更加实用的椭圆绘制与编辑方法,通过控制点完美控制椭圆形状,而且解决了旋转角度问题。先看效果:
本文采用的是三点法绘制椭圆(见下图,手绘有点丑,见谅哈 ☺☺☺)。即通过控制点A、B确定椭圆的长轴(或短轴)的端点,控制点A和控制点B的中点作为椭圆的圆心,然后计算控制点C到线段AB的距离确定短半轴的长度。
当发生旋转时,只需要计算AB所在的直线与正东的夹角即可。注:这里可能有个坑,官方文档里定义的rotation是与正北的逆时针夹角,我实际代码里使用正东夹角才对。各位读者大佬们,如果知道是什么原因,烦请告知我一下,谢谢。
本文使用Cesium中Entity自带的ellipse实现椭圆绘制,具体实现步骤如下:
1、新建CommonEllipseUseCesium类继承BaseDraw
import EventDispatcher from "@/system/EventDispatcher/EventDispatcher";
import { CallbackPositionProperty, CallbackProperty, Cartesian3, ClassificationType, Color, Entity, Viewer } from "cesium";
import { BaseDraw, GeometryType } from "../BaseDraw";
import { CreateEllipse2Points } from "./CreatePolygonPoints";
export default class CommonEllipseUseCesium extends BaseDraw {
/** 临时点,用来实时跟随鼠标 */
private tempCursor = new Cartesian3();
protected get maxPointCount() { return 3; }
constructor(viewer: Viewer, dispatcher: EventDispatcher) {
super(viewer, dispatcher);
this.geometryType = GeometryType.COMMON_ELLIPSE2;
this.minPointCount = 3;
}
protected buildFinalEntity(): Entity {
let positions = this.getPositions();
return this.viewer.entities.add({
position: Cartesian3.midpoint(positions[0], positions[1], new Cartesian3()),
ellipse: {
semiMajorAxis: CreateEllipse2Points(positions).semiMajorAxis,
semiMinorAxis: CreateEllipse2Points(positions).semiMinorAxis,
rotation: CreateEllipse2Points(positions).rotation,
material: Color.BLUE.withAlpha(0.4),
outline: true,
outlineColor: Color.WHITE,
outlineWidth: 2,
classificationType: ClassificationType.BOTH
}
});
}
protected buildTempEntity(): Entity | undefined {
if (this.getPositions().length < (this.minPointCount - 1)) return undefined;
return this.viewer.entities.add({
position: new CallbackPositionProperty(() => Cartesian3.midpoint(this.getPositions()[0], this.getPositions()[1], new Cartesian3()), false),
ellipse: {
semiMajorAxis: new CallbackProperty(() => CreateEllipse2Points([
...this.getPositions(),
this.tempCursor,
]).semiMajorAxis, false),
semiMinorAxis: new CallbackProperty(() => CreateEllipse2Points([
...this.getPositions(),
this.tempCursor,
]).semiMinorAxis, false),
rotation: new CallbackProperty(() => CreateEllipse2Points([
...this.getPositions(),
this.tempCursor,
]).rotation, false),
classificationType: ClassificationType.BOTH,// 贴地
material: Color.CYAN.withAlpha(0.3),
outline: true,
outlineColor: Color.WHITE,
outlineWidth: 2
}
});
}
protected updateTempEntity(cursor: Cartesian3): void {
this.tempCursor = cursor;
}
/* -------------------- 鼠标事件 -------------------- */
protected onLeftClick(e: any): void {
super.onLeftClick(e);
if (this.getPositions().length === this.maxPointCount) {
this.finish();
}
}
;
}
2、在回调函数回调函数CreateEllipse2Points中通过前两个控制点确定长半轴长度及椭圆圆心,然后通过pointToLineDistance计算第三个点到长轴的距离作为短半轴长度,最后计算长轴与正东的夹角。
export const CreateEllipse2Points = (pos: Cartesian3[]) => {
// 1. 圆心
const center = Cartesian3.midpoint(pos[0], pos[1], new Cartesian3());
// 2. 长半轴
let semiMajorAxis = Cartesian3.distance(pos[0], pos[1]) / 2;
// 3. 短半轴
let semiMinorAxis = pointToLineDistance(pos[2], pos[0], pos[1]);
// 4. rotation
// ENU变换
const enuToFixed = Transforms.eastNorthUpToFixedFrame(center);
const fixedToEnu = Matrix4.inverse(enuToFixed, new Matrix4());
const p2ENU = Matrix4.multiplyByPoint(fixedToEnu, pos[1], new Cartesian3());
const centerENU = new Cartesian3(0, 0, 0);
const majorVecENU = Cartesian3.subtract(p2ENU, centerENU, new Cartesian3());
// rotation是与正东(x轴)的夹角
let rotation = Math.atan2(majorVecENU.y, majorVecENU.x);
// 5. 保证semiMajorAxis >= semiMinorAxis
if (semiMajorAxis < semiMinorAxis) {
[semiMajorAxis, semiMinorAxis] = [semiMinorAxis, semiMajorAxis];
rotation += Math.PI / 2;
}
// 归一化到[0, 2PI]
rotation = (rotation + 2 * Math.PI) % (2 * Math.PI);
return {
semiMajorAxis,
semiMinorAxis,
rotation
};
}
3、然后在EditHelper.ts中添加新椭圆的绘制,从而完成编辑功能。
case GeometryType.COMMON_ELLIPSE2:
this.shapeEntity.position = new ConstantPositionProperty(Cartesian3.midpoint(this.positions[0], this.positions[1], new Cartesian3()));
if (this.shapeEntity.ellipse) {
this.shapeEntity.ellipse.semiMajorAxis = new CallbackProperty(() => CreateEllipse2Points(this.positions).semiMajorAxis, false);
this.shapeEntity.ellipse.semiMinorAxis = new CallbackProperty(() => CreateEllipse2Points(this.positions).semiMinorAxis, false);
this.shapeEntity.ellipse.rotation = new CallbackProperty(() => CreateEllipse2Points(this.positions).rotation, false);
}
break;
4、实现效果
5、更多代码请关注原文Cesium+Vue3学习教程系列(22)---一种更加实用的椭圆绘制与编辑方法