1326 lines
40 KiB
TypeScript
Raw Normal View History

// 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
// }
// });
// }
/**
*
*/
printDrawingInfo(drawingId: string): void {
const drawing = this.drawingEntities.get(drawingId);
if (!drawing) {
console.warn(`未找到ID为 ${drawingId} 的空域`);
return;
}
const info = this.generateDrawingInfo(drawing);
this.printFormattedInfo(info);
}
/**
*
*/
printAllDrawingsInfo(): void {
if (this.drawingEntities.size === 0) {
console.log('当前没有绘制任何空域');
return;
}
console.log(`=== 空域信息汇总 (共 ${this.drawingEntities.size} 个) ===`);
this.drawingEntities.forEach((drawing, id) => {
const info = this.generateDrawingInfo(drawing);
this.printFormattedInfo(info);
console.log('---'); // 分隔线
});
}
/**
*
*/
private printFormattedInfo(info: DrawingInfo): void {
const { id, type, area, properties } = info;
console.log(`🛡️ 空域ID: ${id}`);
console.log(`📝 类型: ${type === 'circle' ? '圆形空域' : '多边形空域'}`);
console.log(`📊 面积: ${(area / 1000000).toFixed(3)} km²`);
if (type === 'circle') {
const center = properties.center;
console.log(`📍 圆心坐标:`);
console.log(` 经度: ${center.longitude.toFixed(6)}°`);
console.log(` 纬度: ${center.latitude.toFixed(6)}°`);
console.log(` 高程: ${center.height.toFixed(2)}`);
console.log(`📏 半径: ${(info.radius! / 1000).toFixed(3)} km`);
console.log(`📐 直径: ${(info.radius! * 2 / 1000).toFixed(3)} km`);
console.log(`🔄 周长: ${(properties.circumference / 1000).toFixed(3)} km`);
} else {
const center = properties.center;
const bounds = properties.bounds;
const boundaryPoints = properties.boundaryPoints;
console.log(`📍 中心点坐标:`);
console.log(` 经度: ${center.longitude.toFixed(6)}°`);
console.log(` 纬度: ${center.latitude.toFixed(6)}°`);
console.log(` 高程: ${center.height.toFixed(2)}`);
console.log(`🗺️ 边界范围:`);
console.log(` 北: ${bounds.north.toFixed(6)}°`);
console.log(` 南: ${bounds.south.toFixed(6)}°`);
console.log(` 东: ${bounds.east.toFixed(6)}°`);
console.log(` 西: ${bounds.west.toFixed(6)}°`);
console.log(`📏 尺寸信息:`);
console.log(` 宽度: ${(properties.width / 1000).toFixed(3)} km`);
console.log(` 高度: ${(properties.height / 1000).toFixed(3)} km`);
console.log(` 周长: ${(properties.perimeter / 1000).toFixed(3)} km`);
console.log(`📍 边界顶点 (${boundaryPoints.length}个):`);
boundaryPoints.forEach((point, index) => {
console.log(` 顶点${index + 1}: ${point.longitude.toFixed(6)}°, ${point.latitude.toFixed(6)}°, ${point.height.toFixed(2)}`);
});
}
console.log(`🎨 样式设置:`);
console.log(` 填充颜色: ${properties.options.color}`);
console.log(` 边框颜色: ${properties.options.outlineColor}`);
console.log(` 边框宽度: ${properties.options.outlineWidth}px`);
console.log(` 拉伸高度: ${properties.options.extrudedHeight}`);
}
/**
* JSON格式
*/
getDrawingInfoJSON(drawingId: string): any {
const drawing = this.drawingEntities.get(drawingId);
if (!drawing) return null;
const info = this.generateDrawingInfo(drawing);
return this.formatInfoToJSON(info);
}
/**
* JSON格式
*/
getAllDrawingsInfoJSON(): any[] {
const result = [];
for (const [id, drawing] of this.drawingEntities) {
const info = this.generateDrawingInfo(drawing);
result.push(this.formatInfoToJSON(info));
}
return result;
}
/**
* JSON
*/
private formatInfoToJSON(info: DrawingInfo): any {
const jsonInfo: any = {
id: info.id,
type: info.type,
area: {
squareMeters: info.area,
squareKilometers: info.area / 1000000
},
properties: {
...info.properties,
options: {
color: info.properties.options.color?.toCssColorString(),
outlineColor: info.properties.options.outlineColor?.toCssColorString(),
outlineWidth: info.properties.options.outlineWidth,
extrudedHeight: info.properties.options.extrudedHeight
}
}
};
// 移除循环引用
delete jsonInfo.properties.boundaryPoints;
delete jsonInfo.properties.center;
if (info.type === 'circle') {
jsonInfo.circle = {
center: info.properties.center,
radius: info.radius,
diameter: info.radius! * 2,
circumference: info.properties.circumference
};
} else {
jsonInfo.polygon = {
center: info.properties.center,
bounds: info.properties.bounds,
dimensions: {
width: info.properties.width,
height: info.properties.height,
perimeter: info.properties.perimeter
},
vertexCount: info.properties.boundaryPoints.length,
vertices: info.properties.boundaryPoints
};
}
return jsonInfo;
}
/**
*
*/
exportDrawingInfoAsText(drawingId: string): string {
const drawing = this.drawingEntities.get(drawingId);
if (!drawing) return '';
const info = this.generateDrawingInfo(drawing);
return this.formatInfoAsText(info);
}
/**
*
*/
private formatInfoAsText(info: DrawingInfo): string {
let text = '';
if (info.type === 'circle') {
const center = info.properties.center;
text = `圆形空域信息
空域ID: ${info.id}
圆心坐标: 经度${center.longitude.toFixed(6)}°, ${center.latitude.toFixed(6)}°, ${center.height.toFixed(2)}
半径: ${(info.radius! / 1000).toFixed(3)} km
直径: ${(info.radius! * 2 / 1000).toFixed(3)} km
周长: ${(info.properties.circumference / 1000).toFixed(3)} km
面积: ${(info.area / 1000000).toFixed(3)} km²`;
} else {
const center = info.properties.center;
const bounds = info.properties.bounds;
text = `多边形空域信息
空域ID: ${info.id}
中心点: 经度${center.longitude.toFixed(6)}°, ${center.latitude.toFixed(6)}°, ${center.height.toFixed(2)}
边界范围: 北${bounds.north.toFixed(6)}° ${bounds.south.toFixed(6)}° ${bounds.east.toFixed(6)}° 西${bounds.west.toFixed(6)}°
尺寸: 宽度${(info.properties.width / 1000).toFixed(3)}km ${(info.properties.height / 1000).toFixed(3)}km
周长: ${(info.properties.perimeter / 1000).toFixed(3)} km
面积: ${(info.area / 1000000).toFixed(3)} km²
顶点数量: ${info.properties.boundaryPoints.length}`;
// 添加顶点信息
info.properties.boundaryPoints.forEach((point, index) => {
text += `\n顶点${index + 1}: 经度${point.longitude.toFixed(6)}°, 纬度${point.latitude.toFixed(6)}°, 高程${point.height.toFixed(2)}`;
});
}
return text;
}
/**
*
*/
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)
// });
// }
// }
/**
*
*/
flyToDrawing(id: string, duration: number = 2): void {
console.log('=== flyToDrawing 开始执行 ===');
console.log('目标空域ID:', id);
const drawing = this.drawingEntities.get(id);
if (!drawing) {
console.error('❌ 未找到空域:', id);
return;
}
console.log('找到空域:', drawing);
try {
if (drawing.type === 'circle') {
// 对于圆形,飞到圆心上方
const center = drawing.positions[0];
const radius = drawing.positions.length > 1 ?
Cesium.Cartesian3.distance(center, drawing.positions[1]) :
(drawing.properties.options.radius || 1000);
const height = radius * 2 + 500; // 在半径2倍高度加上500米
console.log('圆形空域 - 圆心:', center);
console.log('圆形空域 - 半径:', radius);
console.log('圆形空域 - 飞行高度:', height);
this.viewer.camera.flyTo({
destination: center,
orientation: {
heading: 0,
pitch: -Math.PI/4,
roll: 0
},
duration: duration,
complete: () => {
console.log('✅ 飞向圆形空域完成');
},
cancel: () => {
console.log('❌ 飞向圆形空域取消');
}
});
} else {
// 对于多边形,飞到边界框
console.log('多边形空域 - 实体:', drawing.entity);
this.viewer.flyTo(drawing.entity, {
duration: duration,
offset: new Cesium.HeadingPitchRange(0, -Math.PI/4, 0),
complete: () => {
console.log('✅ 飞向多边形空域完成');
},
cancel: () => {
console.log('❌ 飞向多边形空域取消');
}
});
}
console.log('✅ flyTo 命令已发送');
} catch (error) {
console.error('❌ 飞向空域时发生错误:', error);
}
}
/**
*
*/
flyToDrawingImproved(id: string, duration: number = 2): void {
console.log('=== flyToDrawingImproved 开始执行 ===');
const drawing = this.drawingEntities.get(id);
if (!drawing) {
console.error('❌ 未找到空域:', id);
return;
}
try {
// 计算实体的边界球
const boundingSphere = this.computeEntityBoundingSphere(drawing.entity);
if (!boundingSphere) {
console.error('❌ 无法计算边界球');
return;
}
console.log('边界球:', boundingSphere);
// 飞到边界球
this.viewer.camera.flyToBoundingSphere(boundingSphere, {
duration: duration,
offset: new Cesium.HeadingPitchRange(0, -Math.PI/4, boundingSphere.radius * 3),
complete: () => {
console.log('✅ 飞向空域完成');
},
cancel: () => {
console.log('❌ 飞向空域取消');
}
});
} catch (error) {
console.error('❌ 飞向空域时发生错误:', error);
}
}
/**
*
*/
private computeEntityBoundingSphere(entity: Cesium.Entity): Cesium.BoundingSphere | null {
try {
// 方法1: 使用 Cesium 的 computeBoundingSphere
if (entity.polygon || entity.ellipse) {
const boundingSphere = entity.computeBoundingSphere();
if (boundingSphere) {
return boundingSphere;
}
}
// 方法2: 手动计算边界球
if (entity.polygon && entity.polygon.hierarchy) {
const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
if (hierarchy && hierarchy.positions) {
return Cesium.BoundingSphere.fromPoints(hierarchy.positions);
}
}
// 方法3: 对于椭圆,使用位置和半径
if (entity.ellipse && entity.position) {
const position = entity.position.getValue(Cesium.JulianDate.now());
const semiMajorAxis = entity.ellipse.semiMajorAxis?.getValue(Cesium.JulianDate.now()) || 1000;
const semiMinorAxis = entity.ellipse.semiMinorAxis?.getValue(Cesium.JulianDate.now()) || 1000;
const radius = Math.max(semiMajorAxis, semiMinorAxis);
return new Cesium.BoundingSphere(position, radius);
}
return null;
} catch (error) {
console.error('计算边界球时发生错误:', error);
return null;
}
}
/**
*
*/
flyToDrawingSimple(id: string, duration: number = 2): void {
console.log('=== flyToDrawingSimple 开始执行 ===');
const drawing = this.drawingEntities.get(id);
if (!drawing) {
console.error('❌ 未找到空域:', id);
return;
}
try {
// 获取实体的位置
const position = drawing.entity.position?.getValue(Cesium.JulianDate.now());
if (!position) {
console.error('❌ 无法获取实体位置');
return;
}
console.log('实体位置:', position);
// 转换到经纬度
const cartographic = Cesium.Cartographic.fromCartesian(position);
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const height = cartographic.height + 2000; // 在实体高度上加2000米
console.log('目标位置 - 经度:', longitude, '纬度:', latitude, '高度:', height);
// 飞到该位置
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(longitude, latitude, height),
orientation: {
heading: 0,
pitch: -Math.PI/3, // -60度俯角
roll: 0
},
duration: duration,
complete: () => {
console.log('✅ 飞向空域完成');
},
cancel: () => {
console.log('❌ 飞向空域取消');
}
});
} catch (error) {
console.error('❌ 飞向空域时发生错误:', error);
}
}
/**
*
*/
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();
}
}