919 lines
27 KiB
TypeScript
919 lines
27 KiB
TypeScript
|
|
// src/utils/cesium/DrawingTool.ts
|
|||
|
|
import * as Cesium from 'cesium';
|
|||
|
|
|
|||
|
|
export interface DrawingOptions {
|
|||
|
|
color?: Cesium.Color;
|
|||
|
|
outlineColor?: Cesium.Color;
|
|||
|
|
outlineWidth?: number;
|
|||
|
|
fill?: boolean;
|
|||
|
|
classificationType?: Cesium.ClassificationType;
|
|||
|
|
height?: number;
|
|||
|
|
extrudedHeight?: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface CircleOptions extends DrawingOptions {
|
|||
|
|
radius?: number;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface PolygonOptions extends DrawingOptions {
|
|||
|
|
closePath?: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
export interface DrawingInfo {
|
|||
|
|
id: string;
|
|||
|
|
type: 'circle' | 'polygon';
|
|||
|
|
positions: Cesium.Cartesian3[];
|
|||
|
|
center?: Cesium.Cartesian3; // 圆心(仅圆形)
|
|||
|
|
radius?: number; // 半径(仅圆形)
|
|||
|
|
bounds?: { // 边界信息(仅多边形)
|
|||
|
|
north: number;
|
|||
|
|
south: number;
|
|||
|
|
east: number;
|
|||
|
|
west: number;
|
|||
|
|
};
|
|||
|
|
area: number;
|
|||
|
|
properties: any;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export interface DrawingResult {
|
|||
|
|
id: string;
|
|||
|
|
type: 'circle' | 'polygon';
|
|||
|
|
positions: Cesium.Cartesian3[];
|
|||
|
|
entity: Cesium.Entity;
|
|||
|
|
properties: any;
|
|||
|
|
info: DrawingInfo; // 新增信息字段
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export class DrawingTool {
|
|||
|
|
private viewer: Cesium.Viewer;
|
|||
|
|
private handler: Cesium.ScreenSpaceEventHandler;
|
|||
|
|
private entities: Cesium.EntityCollection;
|
|||
|
|
|
|||
|
|
private isDrawing: boolean = false;
|
|||
|
|
private currentType: 'circle' | 'polygon' | null = null;
|
|||
|
|
private currentPositions: Cesium.Cartesian3[] = [];
|
|||
|
|
private tempEntities: Cesium.Entity[] = [];
|
|||
|
|
private drawingEntities: Map<string, DrawingResult> = new Map();
|
|||
|
|
|
|||
|
|
private defaultOptions: DrawingOptions = {
|
|||
|
|
color: Cesium.Color.YELLOW.withAlpha(0.3),
|
|||
|
|
outlineColor: Cesium.Color.YELLOW,
|
|||
|
|
outlineWidth: 2,
|
|||
|
|
fill: true,
|
|||
|
|
classificationType: Cesium.ClassificationType.BOTH,
|
|||
|
|
height: 0,
|
|||
|
|
extrudedHeight: 1000
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
private drawingCallbacks: {
|
|||
|
|
onStart?: () => void;
|
|||
|
|
onPointAdd?: (position: Cesium.Cartesian3) => void;
|
|||
|
|
onComplete?: (result: DrawingResult) => void;
|
|||
|
|
onCancel?: () => void;
|
|||
|
|
onClick?: (result: DrawingResult, info: DrawingInfo) => void; // 新增点击回调
|
|||
|
|
} = {};
|
|||
|
|
|
|||
|
|
constructor(viewer: Cesium.Viewer) {
|
|||
|
|
this.viewer = viewer;
|
|||
|
|
this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
|
|||
|
|
this.entities = viewer.entities;
|
|||
|
|
|
|||
|
|
// 初始化点击事件监听
|
|||
|
|
this.initClickHandler();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化点击事件处理器
|
|||
|
|
*/
|
|||
|
|
private initClickHandler(): void {
|
|||
|
|
this.handler.setInputAction((event: any) => {
|
|||
|
|
this.handleEntityClick(event.position);
|
|||
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理实体点击事件
|
|||
|
|
*/
|
|||
|
|
private handleEntityClick(position: Cesium.Cartesian2): void {
|
|||
|
|
const pickedObject = this.viewer.scene.pick(position);
|
|||
|
|
if (!pickedObject || !pickedObject.id) return;
|
|||
|
|
|
|||
|
|
const clickedEntity = pickedObject.id;
|
|||
|
|
const drawingResult = this.findDrawingByEntity(clickedEntity);
|
|||
|
|
|
|||
|
|
if (drawingResult) {
|
|||
|
|
const drawingInfo = this.generateDrawingInfo(drawingResult);
|
|||
|
|
this.drawingCallbacks.onClick?.(drawingResult, drawingInfo);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据实体查找绘图结果
|
|||
|
|
*/
|
|||
|
|
private findDrawingByEntity(entity: Cesium.Entity): DrawingResult | undefined {
|
|||
|
|
for (const drawing of this.drawingEntities.values()) {
|
|||
|
|
if (drawing.entity === entity) {
|
|||
|
|
return drawing;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return undefined;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成绘图信息
|
|||
|
|
*/
|
|||
|
|
private generateDrawingInfo(drawing: DrawingResult): DrawingInfo {
|
|||
|
|
if (drawing.type === 'circle') {
|
|||
|
|
return this.generateCircleInfo(drawing);
|
|||
|
|
} else {
|
|||
|
|
return this.generatePolygonInfo(drawing);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成圆形信息
|
|||
|
|
*/
|
|||
|
|
private generateCircleInfo(drawing: DrawingResult): DrawingInfo {
|
|||
|
|
const center = drawing.positions[0];
|
|||
|
|
const radius = drawing.positions.length > 1 ?
|
|||
|
|
Cesium.Cartesian3.distance(center, drawing.positions[1]) :
|
|||
|
|
(drawing.properties.options.radius || 1000);
|
|||
|
|
|
|||
|
|
const centerCartographic = Cesium.Cartographic.fromCartesian(center);
|
|||
|
|
const centerLongitude = Cesium.Math.toDegrees(centerCartographic.longitude);
|
|||
|
|
const centerLatitude = Cesium.Math.toDegrees(centerCartographic.latitude);
|
|||
|
|
const centerHeight = centerCartographic.height;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id: drawing.id,
|
|||
|
|
type: 'circle',
|
|||
|
|
positions: drawing.positions,
|
|||
|
|
center: center,
|
|||
|
|
radius: radius,
|
|||
|
|
area: drawing.properties.area,
|
|||
|
|
properties: {
|
|||
|
|
...drawing.properties,
|
|||
|
|
center: {
|
|||
|
|
longitude: centerLongitude,
|
|||
|
|
latitude: centerLatitude,
|
|||
|
|
height: centerHeight
|
|||
|
|
},
|
|||
|
|
radius: radius,
|
|||
|
|
circumference: 2 * Math.PI * radius,
|
|||
|
|
diameter: 2 * radius
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成多边形信息
|
|||
|
|
*/
|
|||
|
|
private generatePolygonInfo(drawing: DrawingResult): DrawingInfo {
|
|||
|
|
// 计算边界
|
|||
|
|
let minLon = Infinity, maxLon = -Infinity;
|
|||
|
|
let minLat = Infinity, maxLat = -Infinity;
|
|||
|
|
let minHeight = Infinity, maxHeight = -Infinity;
|
|||
|
|
|
|||
|
|
const boundaryPoints = drawing.positions.map(position => {
|
|||
|
|
const cartographic = Cesium.Cartographic.fromCartesian(position);
|
|||
|
|
const lon = Cesium.Math.toDegrees(cartographic.longitude);
|
|||
|
|
const lat = Cesium.Math.toDegrees(cartographic.latitude);
|
|||
|
|
const height = cartographic.height;
|
|||
|
|
|
|||
|
|
// 更新边界
|
|||
|
|
minLon = Math.min(minLon, lon);
|
|||
|
|
maxLon = Math.max(maxLon, lon);
|
|||
|
|
minLat = Math.min(minLat, lat);
|
|||
|
|
maxLat = Math.max(maxLat, lat);
|
|||
|
|
minHeight = Math.min(minHeight, height);
|
|||
|
|
maxHeight = Math.max(maxHeight, height);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
longitude: lon,
|
|||
|
|
latitude: lat,
|
|||
|
|
height: height
|
|||
|
|
};
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 计算中心点
|
|||
|
|
const centerLon = (minLon + maxLon) / 2;
|
|||
|
|
const centerLat = (minLat + maxLat) / 2;
|
|||
|
|
const centerHeight = (minHeight + maxHeight) / 2;
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id: drawing.id,
|
|||
|
|
type: 'polygon',
|
|||
|
|
positions: drawing.positions,
|
|||
|
|
bounds: {
|
|||
|
|
north: maxLat,
|
|||
|
|
south: minLat,
|
|||
|
|
east: maxLon,
|
|||
|
|
west: minLon
|
|||
|
|
},
|
|||
|
|
area: drawing.properties.area,
|
|||
|
|
properties: {
|
|||
|
|
...drawing.properties,
|
|||
|
|
boundaryPoints: boundaryPoints,
|
|||
|
|
bounds: {
|
|||
|
|
north: maxLat,
|
|||
|
|
south: minLat,
|
|||
|
|
east: maxLon,
|
|||
|
|
west: minLon
|
|||
|
|
},
|
|||
|
|
center: {
|
|||
|
|
longitude: centerLon,
|
|||
|
|
latitude: centerLat,
|
|||
|
|
height: centerHeight
|
|||
|
|
},
|
|||
|
|
width: this.calculateDistance(minLon, minLat, maxLon, minLat), // 东西宽度
|
|||
|
|
height: this.calculateDistance(minLon, minLat, minLon, maxLat), // 南北高度
|
|||
|
|
perimeter: this.calculatePerimeter(drawing.positions)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算两点间距离
|
|||
|
|
*/
|
|||
|
|
private calculateDistance(lon1: number, lat1: number, lon2: number, lat2: number): number {
|
|||
|
|
const cartesian1 = Cesium.Cartesian3.fromDegrees(lon1, lat1);
|
|||
|
|
const cartesian2 = Cesium.Cartesian3.fromDegrees(lon2, lat2);
|
|||
|
|
return Cesium.Cartesian3.distance(cartesian1, cartesian2);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算多边形周长
|
|||
|
|
*/
|
|||
|
|
private calculatePerimeter(positions: Cesium.Cartesian3[]): number {
|
|||
|
|
let perimeter = 0;
|
|||
|
|
const n = positions.length;
|
|||
|
|
|
|||
|
|
for (let i = 0; i < n; i++) {
|
|||
|
|
const j = (i + 1) % n;
|
|||
|
|
perimeter += Cesium.Cartesian3.distance(positions[i], positions[j]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return perimeter;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 开始绘制圆形
|
|||
|
|
*/
|
|||
|
|
startDrawingCircle(options: CircleOptions = {}): void {
|
|||
|
|
this.startDrawing('circle', options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 开始绘制多边形
|
|||
|
|
*/
|
|||
|
|
startDrawingPolygon(options: PolygonOptions = {}): void {
|
|||
|
|
this.startDrawing('polygon', options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 开始绘制
|
|||
|
|
*/
|
|||
|
|
private startDrawing(type: 'circle' | 'polygon', options: DrawingOptions = {}): void {
|
|||
|
|
if (this.isDrawing) {
|
|||
|
|
this.cancelDrawing();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.isDrawing = true;
|
|||
|
|
this.currentType = type;
|
|||
|
|
this.currentPositions = [];
|
|||
|
|
this.tempEntities = [];
|
|||
|
|
|
|||
|
|
const mergedOptions = { ...this.defaultOptions, ...options };
|
|||
|
|
|
|||
|
|
// 设置鼠标样式
|
|||
|
|
this.viewer.scene.canvas.style.cursor = 'crosshair';
|
|||
|
|
|
|||
|
|
// 左键点击添加点
|
|||
|
|
this.handler.setInputAction((event: any) => {
|
|||
|
|
this.handleLeftClick(event.position, mergedOptions);
|
|||
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|||
|
|
|
|||
|
|
// 鼠标移动预览
|
|||
|
|
this.handler.setInputAction((event: any) => {
|
|||
|
|
this.handleMouseMove(event.endPosition, mergedOptions);
|
|||
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|||
|
|
|
|||
|
|
// 右键完成绘制
|
|||
|
|
this.handler.setInputAction(() => {
|
|||
|
|
this.completeDrawing(mergedOptions);
|
|||
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
|
|||
|
|
|
|||
|
|
// 取消绘制
|
|||
|
|
const cancelHandler = (event: KeyboardEvent) => {
|
|||
|
|
if (event.key === 'Escape') {
|
|||
|
|
this.cancelDrawing();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
document.addEventListener('keydown', cancelHandler);
|
|||
|
|
(this as any).cancelHandler = cancelHandler;
|
|||
|
|
|
|||
|
|
this.drawingCallbacks.onStart?.();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理左键点击
|
|||
|
|
*/
|
|||
|
|
private handleLeftClick(position: Cesium.Cartesian2, options: DrawingOptions): void {
|
|||
|
|
const cartesian = this.pickCoordinate(position);
|
|||
|
|
if (!cartesian) return;
|
|||
|
|
|
|||
|
|
this.currentPositions.push(cartesian);
|
|||
|
|
|
|||
|
|
// 添加临时点标记
|
|||
|
|
const pointEntity = this.entities.add({
|
|||
|
|
position: cartesian,
|
|||
|
|
point: {
|
|||
|
|
pixelSize: 8,
|
|||
|
|
color: Cesium.Color.RED,
|
|||
|
|
outlineColor: Cesium.Color.WHITE,
|
|||
|
|
outlineWidth: 2,
|
|||
|
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.tempEntities.push(pointEntity);
|
|||
|
|
|
|||
|
|
this.drawingCallbacks.onPointAdd?.(cartesian);
|
|||
|
|
|
|||
|
|
// 如果是圆形,第一个点确定圆心,第二个点确定半径
|
|||
|
|
if (this.currentType === 'circle' && this.currentPositions.length === 2) {
|
|||
|
|
this.completeDrawing(options);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 处理鼠标移动
|
|||
|
|
*/
|
|||
|
|
private handleMouseMove(position: Cesium.Cartesian2, options: DrawingOptions): void {
|
|||
|
|
if (this.currentPositions.length === 0) return;
|
|||
|
|
|
|||
|
|
const cartesian = this.pickCoordinate(position);
|
|||
|
|
if (!cartesian) return;
|
|||
|
|
|
|||
|
|
// 清除之前的临时图形
|
|||
|
|
this.clearTempShapes();
|
|||
|
|
|
|||
|
|
if (this.currentType === 'circle') {
|
|||
|
|
this.drawTempCircle(cartesian, options);
|
|||
|
|
} else if (this.currentType === 'polygon') {
|
|||
|
|
this.drawTempPolygon(cartesian, options);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 绘制临时圆形
|
|||
|
|
*/
|
|||
|
|
private drawTempCircle(mousePosition: Cesium.Cartesian3, options: CircleOptions): void {
|
|||
|
|
const center = this.currentPositions[0];
|
|||
|
|
const radius = Cesium.Cartesian3.distance(center, mousePosition);
|
|||
|
|
|
|||
|
|
const circleEntity = this.entities.add({
|
|||
|
|
position: center,
|
|||
|
|
ellipse: {
|
|||
|
|
semiMinorAxis: radius,
|
|||
|
|
semiMajorAxis: radius,
|
|||
|
|
material: options.color || this.defaultOptions.color,
|
|||
|
|
outline: true,
|
|||
|
|
outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
height: options.height || this.defaultOptions.height,
|
|||
|
|
extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.tempEntities.push(circleEntity);
|
|||
|
|
|
|||
|
|
// 绘制半径线
|
|||
|
|
if (this.currentPositions.length === 1) {
|
|||
|
|
const radiusLine = this.entities.add({
|
|||
|
|
polyline: {
|
|||
|
|
positions: [center, mousePosition],
|
|||
|
|
width: 2,
|
|||
|
|
material: Cesium.Color.WHITE.withAlpha(0.7)
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.tempEntities.push(radiusLine);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 绘制临时多边形
|
|||
|
|
*/
|
|||
|
|
private drawTempPolygon(mousePosition: Cesium.Cartesian3, options: PolygonOptions): void {
|
|||
|
|
const positions = [...this.currentPositions, mousePosition];
|
|||
|
|
|
|||
|
|
// 绘制临时多边形
|
|||
|
|
const polygonEntity = this.entities.add({
|
|||
|
|
polygon: {
|
|||
|
|
hierarchy: new Cesium.PolygonHierarchy(positions),
|
|||
|
|
material: options.color || this.defaultOptions.color,
|
|||
|
|
outline: true,
|
|||
|
|
outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
height: options.height || this.defaultOptions.height,
|
|||
|
|
extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.tempEntities.push(polygonEntity);
|
|||
|
|
|
|||
|
|
// 绘制临时边线
|
|||
|
|
if (positions.length > 1) {
|
|||
|
|
const linePositions = [...positions];
|
|||
|
|
if (positions.length > 2) {
|
|||
|
|
linePositions.push(positions[0]); // 闭合多边形
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const lineEntity = this.entities.add({
|
|||
|
|
polyline: {
|
|||
|
|
positions: linePositions,
|
|||
|
|
width: 3,
|
|||
|
|
material: options.outlineColor || this.defaultOptions.outlineColor
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
this.tempEntities.push(lineEntity);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 完成绘制
|
|||
|
|
*/
|
|||
|
|
// private completeDrawing(options: DrawingOptions): void {
|
|||
|
|
// if (this.currentPositions.length < 2) {
|
|||
|
|
// this.cancelDrawing();
|
|||
|
|
// return;
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// const id = `drawing_${Date.now()}`;
|
|||
|
|
// let entity: Cesium.Entity;
|
|||
|
|
|
|||
|
|
// if (this.currentType === 'circle') {
|
|||
|
|
// entity = this.createCircleEntity(id, this.currentPositions, options as CircleOptions);
|
|||
|
|
// } else {
|
|||
|
|
// entity = this.createPolygonEntity(id, this.currentPositions, options as PolygonOptions);
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
// const result: DrawingResult = {
|
|||
|
|
// id,
|
|||
|
|
// type: this.currentType!,
|
|||
|
|
// positions: this.currentPositions,
|
|||
|
|
// entity,
|
|||
|
|
// properties: {
|
|||
|
|
// area: this.calculateArea(this.currentPositions, this.currentType!),
|
|||
|
|
// options
|
|||
|
|
// }
|
|||
|
|
// };
|
|||
|
|
|
|||
|
|
// this.drawingEntities.set(id, result);
|
|||
|
|
// this.cleanupDrawing();
|
|||
|
|
|
|||
|
|
// this.drawingCallbacks.onComplete?.(result);
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 完成绘制
|
|||
|
|
*/
|
|||
|
|
private completeDrawing(options: DrawingOptions): void {
|
|||
|
|
if (this.currentPositions.length < 2) {
|
|||
|
|
this.cancelDrawing();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const id = `drawing_${Date.now()}`;
|
|||
|
|
let entity: Cesium.Entity;
|
|||
|
|
|
|||
|
|
if (this.currentType === 'circle') {
|
|||
|
|
entity = this.createCircleEntity(id, this.currentPositions, options as CircleOptions);
|
|||
|
|
} else {
|
|||
|
|
entity = this.createPolygonEntity(id, this.currentPositions, options as PolygonOptions);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成绘图信息
|
|||
|
|
const drawingInfo = this.currentType === 'circle' ?
|
|||
|
|
this.generateCircleInfo({ id, type: 'circle', positions: this.currentPositions, entity, properties: { options, area: 0 } } as DrawingResult) :
|
|||
|
|
this.generatePolygonInfo({ id, type: 'polygon', positions: this.currentPositions, entity, properties: { options, area: 0 } } as DrawingResult);
|
|||
|
|
|
|||
|
|
// 计算面积
|
|||
|
|
const area = this.calculateArea(this.currentPositions, this.currentType!);
|
|||
|
|
|
|||
|
|
const result: DrawingResult = {
|
|||
|
|
id,
|
|||
|
|
type: this.currentType!,
|
|||
|
|
positions: this.currentPositions,
|
|||
|
|
entity,
|
|||
|
|
properties: {
|
|||
|
|
area: area,
|
|||
|
|
options
|
|||
|
|
},
|
|||
|
|
info: {
|
|||
|
|
...drawingInfo,
|
|||
|
|
area: area
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.drawingEntities.set(id, result);
|
|||
|
|
this.cleanupDrawing();
|
|||
|
|
|
|||
|
|
this.drawingCallbacks.onComplete?.(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建圆形实体
|
|||
|
|
*/
|
|||
|
|
// private createCircleEntity(id: string, positions: Cesium.Cartesian3[], options: CircleOptions): Cesium.Entity {
|
|||
|
|
// const center = positions[0];
|
|||
|
|
// const radius = positions.length > 1 ? Cesium.Cartesian3.distance(center, positions[1]) : (options.radius || 1000);
|
|||
|
|
|
|||
|
|
// return this.entities.add({
|
|||
|
|
// id,
|
|||
|
|
// position: center,
|
|||
|
|
// ellipse: {
|
|||
|
|
// semiMinorAxis: radius,
|
|||
|
|
// semiMajorAxis: radius,
|
|||
|
|
// material: options.color || this.defaultOptions.color,
|
|||
|
|
// outline: true,
|
|||
|
|
// outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
// outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
// height: options.height || this.defaultOptions.height,
|
|||
|
|
// extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
// classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
// },
|
|||
|
|
// label: {
|
|||
|
|
// text: `圆形空域\n半径: ${(radius / 1000).toFixed(2)}km`,
|
|||
|
|
// font: '14pt sans-serif',
|
|||
|
|
// pixelOffset: new Cesium.Cartesian2(0, -50),
|
|||
|
|
// fillColor: Cesium.Color.WHITE,
|
|||
|
|
// outlineColor: Cesium.Color.BLACK,
|
|||
|
|
// outlineWidth: 2,
|
|||
|
|
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|||
|
|
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|||
|
|
// }
|
|||
|
|
// });
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建圆形实体
|
|||
|
|
*/
|
|||
|
|
private createCircleEntity(id: string, positions: Cesium.Cartesian3[], options: CircleOptions): Cesium.Entity {
|
|||
|
|
const center = positions[0];
|
|||
|
|
const radius = positions.length > 1 ? Cesium.Cartesian3.distance(center, positions[1]) : (options.radius || 1000);
|
|||
|
|
const centerCartographic = Cesium.Cartographic.fromCartesian(center);
|
|||
|
|
|
|||
|
|
return this.entities.add({
|
|||
|
|
id,
|
|||
|
|
position: center,
|
|||
|
|
ellipse: {
|
|||
|
|
semiMinorAxis: radius,
|
|||
|
|
semiMajorAxis: radius,
|
|||
|
|
material: options.color || this.defaultOptions.color,
|
|||
|
|
outline: true,
|
|||
|
|
outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
height: options.height || this.defaultOptions.height,
|
|||
|
|
extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
},
|
|||
|
|
label: {
|
|||
|
|
text: `圆形空域\n半径: ${(radius / 1000).toFixed(2)}km`,
|
|||
|
|
font: '14pt sans-serif',
|
|||
|
|
pixelOffset: new Cesium.Cartesian2(0, -50),
|
|||
|
|
fillColor: Cesium.Color.WHITE,
|
|||
|
|
outlineColor: Cesium.Color.BLACK,
|
|||
|
|
outlineWidth: 2,
|
|||
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|||
|
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|||
|
|
},
|
|||
|
|
// 添加自定义属性便于识别
|
|||
|
|
drawingType: 'circle',
|
|||
|
|
drawingId: id
|
|||
|
|
});
|
|||
|
|
console.log("圆形空域:",)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建多边形实体
|
|||
|
|
*/
|
|||
|
|
private createPolygonEntity(id: string, positions: Cesium.Cartesian3[], options: PolygonOptions): Cesium.Entity {
|
|||
|
|
const hierarchy = new Cesium.PolygonHierarchy(positions);
|
|||
|
|
const area = this.calculateArea(positions, 'polygon');
|
|||
|
|
|
|||
|
|
return this.entities.add({
|
|||
|
|
id,
|
|||
|
|
polygon: {
|
|||
|
|
hierarchy,
|
|||
|
|
material: options.color || this.defaultOptions.color,
|
|||
|
|
outline: true,
|
|||
|
|
outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
height: options.height || this.defaultOptions.height,
|
|||
|
|
extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
},
|
|||
|
|
label: {
|
|||
|
|
text: `多边形空域\n面积: ${(area / 1000000).toFixed(2)}km²`,
|
|||
|
|
font: '14pt sans-serif',
|
|||
|
|
pixelOffset: new Cesium.Cartesian2(0, -50),
|
|||
|
|
fillColor: Cesium.Color.WHITE,
|
|||
|
|
outlineColor: Cesium.Color.BLACK,
|
|||
|
|
outlineWidth: 2,
|
|||
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|||
|
|
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|||
|
|
},
|
|||
|
|
// 添加自定义属性便于识别
|
|||
|
|
drawingType: 'polygon',
|
|||
|
|
drawingId: id
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建多边形实体
|
|||
|
|
*/
|
|||
|
|
// private createPolygonEntity(id: string, positions: Cesium.Cartesian3[], options: PolygonOptions): Cesium.Entity {
|
|||
|
|
// const hierarchy = new Cesium.PolygonHierarchy(positions);
|
|||
|
|
// const area = this.calculateArea(positions, 'polygon');
|
|||
|
|
|
|||
|
|
// return this.entities.add({
|
|||
|
|
// id,
|
|||
|
|
// polygon: {
|
|||
|
|
// hierarchy,
|
|||
|
|
// material: options.color || this.defaultOptions.color,
|
|||
|
|
// outline: true,
|
|||
|
|
// outlineColor: options.outlineColor || this.defaultOptions.outlineColor,
|
|||
|
|
// outlineWidth: options.outlineWidth || this.defaultOptions.outlineWidth,
|
|||
|
|
// height: options.height || this.defaultOptions.height,
|
|||
|
|
// extrudedHeight: options.extrudedHeight || this.defaultOptions.extrudedHeight,
|
|||
|
|
// classificationType: options.classificationType || this.defaultOptions.classificationType
|
|||
|
|
// },
|
|||
|
|
// label: {
|
|||
|
|
// text: `多边形空域\n面积: ${(area / 1000000).toFixed(2)}km²`,
|
|||
|
|
// font: '14pt sans-serif',
|
|||
|
|
// pixelOffset: new Cesium.Cartesian2(0, -50),
|
|||
|
|
// fillColor: Cesium.Color.WHITE,
|
|||
|
|
// outlineColor: Cesium.Color.BLACK,
|
|||
|
|
// outlineWidth: 2,
|
|||
|
|
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
|
|||
|
|
// heightReference: Cesium.HeightReference.CLAMP_TO_GROUND
|
|||
|
|
// }
|
|||
|
|
// });
|
|||
|
|
// }
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算面积
|
|||
|
|
*/
|
|||
|
|
private calculateArea(positions: Cesium.Cartesian3[], type: 'circle' | 'polygon'): number {
|
|||
|
|
if (type === 'circle') {
|
|||
|
|
const center = positions[0];
|
|||
|
|
console.log("圆形的圆心:",center)
|
|||
|
|
const radius = positions.length > 1 ? Cesium.Cartesian3.distance(center, positions[1]) : 0;
|
|||
|
|
console.log("圆形的半径:",radius)
|
|||
|
|
return Math.PI * radius * radius;
|
|||
|
|
} else {
|
|||
|
|
// 计算多边形面积
|
|||
|
|
// const cartographics = positions.map(pos =>
|
|||
|
|
// Cesium.Cartographic.fromCartesian(pos)
|
|||
|
|
// );
|
|||
|
|
// return Cesium.PolygonGeometry.computeArea(cartographics);
|
|||
|
|
|
|||
|
|
// 修正:使用正确的多边形面积计算方法
|
|||
|
|
const area = this.computePolygonAreaSimple(positions);
|
|||
|
|
console.log(`Polygon area: ${area} m², vertices: ${positions.length}`);
|
|||
|
|
return area;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 简化但有效的多边形面积计算
|
|||
|
|
*/
|
|||
|
|
private computePolygonAreaSimple(positions: Cesium.Cartesian3[]): number {
|
|||
|
|
if (positions.length < 3) return 0;
|
|||
|
|
|
|||
|
|
// 将3D坐标转换为2D平面坐标(使用第一个点作为参考平面)
|
|||
|
|
const normal = new Cesium.Cartesian3();
|
|||
|
|
Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(positions[0], normal);
|
|||
|
|
|
|||
|
|
let area = 0;
|
|||
|
|
const n = positions.length;
|
|||
|
|
|
|||
|
|
for (let i = 0; i < n; i++) {
|
|||
|
|
const j = (i + 1) % n;
|
|||
|
|
|
|||
|
|
const p1 = positions[i];
|
|||
|
|
const p2 = positions[j];
|
|||
|
|
|
|||
|
|
// 计算两个向量在切平面上的投影
|
|||
|
|
const v1 = Cesium.Cartesian3.subtract(p1, positions[0], new Cesium.Cartesian3());
|
|||
|
|
const v2 = Cesium.Cartesian3.subtract(p2, positions[0], new Cesium.Cartesian3());
|
|||
|
|
|
|||
|
|
// 计算叉积的模长(平行四边形面积)
|
|||
|
|
const cross = Cesium.Cartesian3.cross(v1, v2, new Cesium.Cartesian3());
|
|||
|
|
const parallelogramArea = Cesium.Cartesian3.magnitude(cross);
|
|||
|
|
|
|||
|
|
area += parallelogramArea;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 三角形面积是平行四边形面积的一半
|
|||
|
|
return Math.abs(area) / 2;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 取消绘制
|
|||
|
|
*/
|
|||
|
|
cancelDrawing(): void {
|
|||
|
|
this.cleanupDrawing();
|
|||
|
|
this.drawingCallbacks.onCancel?.();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理绘制状态
|
|||
|
|
*/
|
|||
|
|
private cleanupDrawing(): void {
|
|||
|
|
this.isDrawing = false;
|
|||
|
|
this.currentType = null;
|
|||
|
|
this.currentPositions = [];
|
|||
|
|
|
|||
|
|
// 清除临时实体
|
|||
|
|
this.tempEntities.forEach(entity => this.entities.remove(entity));
|
|||
|
|
this.tempEntities = [];
|
|||
|
|
|
|||
|
|
// 移除事件监听
|
|||
|
|
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
|
|||
|
|
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE);
|
|||
|
|
this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
|
|||
|
|
|
|||
|
|
// 移除键盘事件
|
|||
|
|
if ((this as any).cancelHandler) {
|
|||
|
|
document.removeEventListener('keydown', (this as any).cancelHandler);
|
|||
|
|
(this as any).cancelHandler = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 恢复鼠标样式
|
|||
|
|
this.viewer.scene.canvas.style.cursor = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除临时图形
|
|||
|
|
*/
|
|||
|
|
private clearTempShapes(): void {
|
|||
|
|
this.tempEntities.forEach(entity => this.entities.remove(entity));
|
|||
|
|
this.tempEntities = [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 拾取坐标
|
|||
|
|
*/
|
|||
|
|
private pickCoordinate(position: Cesium.Cartesian2): Cesium.Cartesian3 | null {
|
|||
|
|
const ray = this.viewer.camera.getPickRay(position);
|
|||
|
|
if (!ray) return null;
|
|||
|
|
|
|||
|
|
const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene) ||
|
|||
|
|
this.viewer.scene.camera.pickEllipsoid(position, this.viewer.scene.globe.ellipsoid);
|
|||
|
|
return cartesian;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 设置绘图回调
|
|||
|
|
*/
|
|||
|
|
setCallbacks(callbacks: typeof this.drawingCallbacks): void {
|
|||
|
|
this.drawingCallbacks = { ...this.drawingCallbacks, ...callbacks };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取所有绘制的图形
|
|||
|
|
*/
|
|||
|
|
getAllDrawings(): DrawingResult[] {
|
|||
|
|
return Array.from(this.drawingEntities.values());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 根据ID获取图形
|
|||
|
|
*/
|
|||
|
|
getDrawing(id: string): DrawingResult | undefined {
|
|||
|
|
return this.drawingEntities.get(id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 移除图形
|
|||
|
|
*/
|
|||
|
|
removeDrawing(id: string): boolean {
|
|||
|
|
const drawing = this.drawingEntities.get(id);
|
|||
|
|
if (drawing) {
|
|||
|
|
this.entities.remove(drawing.entity);
|
|||
|
|
this.drawingEntities.delete(id);
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除所有图形
|
|||
|
|
*/
|
|||
|
|
clearAllDrawings(): void {
|
|||
|
|
this.drawingEntities.forEach(drawing => {
|
|||
|
|
this.entities.remove(drawing.entity);
|
|||
|
|
});
|
|||
|
|
this.drawingEntities.clear();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 高亮显示图形
|
|||
|
|
*/
|
|||
|
|
highlightDrawing(id: string, highlight: boolean = true): void {
|
|||
|
|
const drawing = this.drawingEntities.get(id);
|
|||
|
|
if (!drawing) return;
|
|||
|
|
|
|||
|
|
if (drawing.type === 'circle' && drawing.entity.ellipse) {
|
|||
|
|
drawing.entity.ellipse.outlineColor = highlight ?
|
|||
|
|
Cesium.Color.RED :
|
|||
|
|
(drawing.properties.options.outlineColor || this.defaultOptions.outlineColor);
|
|||
|
|
drawing.entity.ellipse.outlineWidth = highlight ? 4 : 2;
|
|||
|
|
} else if (drawing.entity.polygon) {
|
|||
|
|
drawing.entity.polygon.outlineColor = highlight ?
|
|||
|
|
Cesium.Color.RED :
|
|||
|
|
(drawing.properties.options.outlineColor || this.defaultOptions.outlineColor);
|
|||
|
|
drawing.entity.polygon.outlineWidth = highlight ? 4 : 2;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 飞向图形
|
|||
|
|
*/
|
|||
|
|
flyToDrawing(id: string, duration: number = 2): void {
|
|||
|
|
console.log("飞飞飞非法欸")
|
|||
|
|
const drawing = this.drawingEntities.get(id);
|
|||
|
|
console.log("飞飞飞非111")
|
|||
|
|
if (drawing) {
|
|||
|
|
console.log("飞飞飞222")
|
|||
|
|
this.viewer.flyTo(drawing.entity, {
|
|||
|
|
duration: duration,
|
|||
|
|
offset: new Cesium.HeadingPitchRange(0, -0.5, 0)
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 导出图形数据
|
|||
|
|
*/
|
|||
|
|
exportDrawings(): any[] {
|
|||
|
|
return this.getAllDrawings().map(drawing => ({
|
|||
|
|
id: drawing.id,
|
|||
|
|
type: drawing.type,
|
|||
|
|
positions: drawing.positions.map(pos => {
|
|||
|
|
const cartographic = Cesium.Cartographic.fromCartesian(pos);
|
|||
|
|
return {
|
|||
|
|
longitude: Cesium.Math.toDegrees(cartographic.longitude),
|
|||
|
|
latitude: Cesium.Math.toDegrees(cartographic.latitude),
|
|||
|
|
height: cartographic.height
|
|||
|
|
};
|
|||
|
|
}),
|
|||
|
|
properties: drawing.properties
|
|||
|
|
}));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 导入图形数据
|
|||
|
|
*/
|
|||
|
|
importDrawings(data: any[]): void {
|
|||
|
|
data.forEach(item => {
|
|||
|
|
const positions = item.positions.map((pos: any) =>
|
|||
|
|
Cesium.Cartesian3.fromDegrees(pos.longitude, pos.latitude, pos.height)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
let entity: Cesium.Entity;
|
|||
|
|
if (item.type === 'circle') {
|
|||
|
|
entity = this.createCircleEntity(item.id, positions, item.properties.options);
|
|||
|
|
} else {
|
|||
|
|
entity = this.createPolygonEntity(item.id, positions, item.properties.options);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const drawing: DrawingResult = {
|
|||
|
|
id: item.id,
|
|||
|
|
type: item.type,
|
|||
|
|
positions,
|
|||
|
|
entity,
|
|||
|
|
properties: item.properties
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.drawingEntities.set(item.id, drawing);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 销毁
|
|||
|
|
*/
|
|||
|
|
destroy(): void {
|
|||
|
|
this.cancelDrawing();
|
|||
|
|
this.clearAllDrawings();
|
|||
|
|
this.handler.destroy();
|
|||
|
|
}
|
|||
|
|
}
|