Vue3+Cesium开发教程(20)---圆形、扇形绘制与编辑

86 阅读3分钟

本学习系列将以Cesium + Vue3 + Vite +Typescript+elementplus作为主要技术栈展开,后续会循序渐进,持续探索Cesium的高级功能,详情请关注原文Cesium+Vue3学习教程系列(20)---圆形、扇形绘制与编辑

上篇Cesium+Vue3学习教程系列(19)---多边形标绘代码封装修改,新增矩形绘制介绍了矩形的绘制与编辑,为了响应读者的留言增加圆形、扇形的需求,本篇在普通标绘组件中增加圆形、扇形的标绘与编辑。先看效果:

1、圆形标绘。

(1)通过查阅Cesium官方文档,发现Entity里面有EllipseGraphics椭圆类型,椭圆的长半轴与短半轴相等不就是一个圆嘛,因此本篇打算使用ellipse写圆形。话不多说,新建CommonCircle类继承BaseDraw。

import EventDispatcher from "@/system/EventDispatcher/EventDispatcher";
import { CallbackProperty, Cartesian3, ClassificationType, Color, Entity, Viewer } from "cesium";
import { BaseDraw, GeometryType } from "../BaseDraw";
import { circleRadiusCallback } from "./CreatePolygonPoints";
export default class CommonCircle extends BaseDraw {
    /** 临时点,用来实时跟随鼠标 */
    private tempCursor = new Cartesian3();
    protected get maxPointCount() { return 2; }
    constructor(viewer: Viewer, dispatcher: EventDispatcher) {
        super(viewer, dispatcher);
        this.geometryType = GeometryType.COMMON_CIRCLE;
        this.minPointCount = 2;
    }
    protected buildFinalEntity(): Entity {
        return this.viewer.entities.add({
            position: this.getPositions()[0],
            ellipse: {
                semiMajorAxis: circleRadiusCallback(this.getPositions()),
                semiMinorAxis: circleRadiusCallback(this.getPositions()),
               
                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: this.getPositions()[0],
            ellipse: {
                semiMajorAxis: new CallbackProperty(() => circleRadiusCallback([
                    ...this.getPositions(),
                    this.tempCursor,
                ]), false),
                semiMinorAxis: new CallbackProperty(() => circleRadiusCallback([
                    ...this.getPositions(),
                    this.tempCursor,
                ]), 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)采用两点画圆。因此minPointCount 、maxPointCount均为2。长半轴semiMajorAxis和短半轴semiMinorAxis均等于圆形半径就行。

(3)计算半径的回调函数。比较简单,就是计算笛卡尔坐标系下两点的距离。

export const circleRadiusCallback = (pos: Cartesian3[]) => {
    if (pos.length !== 2) return 0;
    return Cartesian3.distance(pos[0], pos[1]);
}

2、扇形绘制。

(1)由于Cesium并没有提供扇形的相关类,因此只能根据Polygon进行模拟绘制。新建CommonSector类继承BasePolygon,通过三个点绘制扇形。第一个点是圆心,第二个点确定圆的半径,第三个点确定扇形角度。

import EventDispatcher from "@/system/EventDispatcher/EventDispatcher";
import { Viewer } from "cesium";
import BasePolygon from "./BasePolygon";
import { GeometryType } from "../BaseDraw";
import { CreateSectorPoints } from "./CreatePolygonPoints";
export default class CommonSector  extends BasePolygon { 
    constructor(viewer: Viewer, dispatcher: EventDispatcher) {
        super(viewer, dispatcher);
        this.geometryType = GeometryType.COMMON_SECTOR;
        this.minPointCount = 3;
    }
    protected get maxPointCount() { return 3; }
    protected createPolygonPoints = CreateSectorPoints;
}

(2)将坐标系转到局部东北天(ENU)坐标系,在局部东北天坐标系下计算平面角度,避免球面曲率干扰。再通过角度插值计算圆弧点坐标,最后再将局部坐标系转为笛卡尔坐标系完成扇形绘制。

/**
 * 创建扇形数据点
 * @param pos 传入的三个点
 * @returns 
 */
export const CreateSectorPoints = (pos: Cartesian3[]) => {
    if (pos.length !== 3) return pos;
    let center = pos[0];
    let p1 = pos[1];
    let p2 = pos[2];
    // 1. 半径
    const radius = Cartesian3.distance(center, p1);
    // 2. 把三点转到局部 ENU 平面,方便算夹角
    const toLocal = Transforms.eastNorthUpToFixedFrame(center);
    const inv = Matrix4.inverse(toLocal, new Matrix4());
    const q1 = Matrix4.multiplyByPoint(inv, p1, new Cartesian3());
    const q2 = Matrix4.multiplyByPoint(inv, p2, new Cartesian3());
    // 3. 起始角、终止角
    let start = Math.atan2(q1.y, q1.x);
    let end = Math.atan2(q2.y, q2.x);
    if (end < start) end += CesiumMath.TWO_PI;   // 保证逆时针
    // 4. 三角网:圆心 + 圆弧插值
    const positions: number[][] = [];
    const N = 64;               // 插值数,可改
    for (let i = 0; i <= N; i++) {
        const angle = start + (end - start) * (i / N);
        const x = radius * Math.cos(angle);
        const y = radius * Math.sin(angle);
        const z = 0;
        positions.push([x, y, z]);
    }
    //ENU 转回 WGS84
    const worldPositions: Cartesian3[] = [];
    // 先把圆心(世界坐标)放进去
    worldPositions.push(center);
    // 再把圆弧上的点逐个转回世界
    for (let i = 0; i < positions.length; i++) {
        const local = new Cartesian3(positions[i][0], positions[i][1], positions[i][2]);
        const world = Matrix4.multiplyByPoint(toLocal, local, new Cartesian3());
        worldPositions.push(world);
    }
    return worldPositions;
}

3、然后将圆形与扇形添加到EditHelper类中updateShape()函数中实现编辑功能。

 case GeometryType.COMMON_CIRCLE:
                this.shapeEntity.position = new ConstantPositionProperty(this.positions[0]);
                if (this.shapeEntity.ellipse) {
                    this.shapeEntity.ellipse.semiMajorAxis = new CallbackProperty(() => circleRadiusCallback(this.positions), false);
                    this.shapeEntity.ellipse.semiMinorAxis = new CallbackProperty(() => circleRadiusCallback(this.positions), false);
                }
                break;
            case GeometryType.COMMON_SECTOR:
                this.shapeEntity.polygon!.hierarchy = new CallbackProperty(() => new PolygonHierarchy(CreateSectorPoints(this.positions)), false);
                break;

4、最终效果