cesium地图空域的导入导出及定位
This commit is contained in:
parent
838d0878aa
commit
e8b8d12623
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@ -2,7 +2,6 @@
|
||||
export {}
|
||||
declare global {
|
||||
const EffectScope: typeof import('vue')['EffectScope']
|
||||
const ElMessage: typeof import('element-plus/es')['ElMessage']
|
||||
const computed: typeof import('vue')['computed']
|
||||
const createApp: typeof import('vue')['createApp']
|
||||
const customRef: typeof import('vue')['customRef']
|
||||
|
||||
@ -74,6 +74,17 @@
|
||||
@print-drawing-info="handlePrintDrawingInfo"
|
||||
@export-drawing-info="handleExportDrawingInfo"
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<!-- 导入工具面板 -->
|
||||
<ImportToolPanel
|
||||
v-if="isInitialized"
|
||||
:import-from-file="importFromFile"
|
||||
:import-airspace-data="importAirspaceData"
|
||||
:get-import-statistics="getImportStatistics"
|
||||
@import-complete="handleImportComplete"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -88,6 +99,7 @@ import { useDrawingManager } from './components/useDrawingManager';
|
||||
import ModelControlPanel from './ModelControlPanel.vue';
|
||||
import CoordinatePickerPanel from './CoordinatePickerPanel.vue';
|
||||
import DrawingToolPanel from './DrawingToolPanel.vue';
|
||||
import ImportToolPanel from './ImportToolPanel.vue';
|
||||
|
||||
|
||||
const cesiumContainer = ref<HTMLElement>();
|
||||
@ -161,7 +173,11 @@ const {
|
||||
printSelectedDrawingInfo,
|
||||
printAllDrawingsInfo,
|
||||
printDrawingInfo,
|
||||
exportSelectedDrawingAsText
|
||||
exportSelectedDrawingAsText,
|
||||
importAirspaceData,
|
||||
importFromFile,
|
||||
importFromGeoJSON,
|
||||
getImportStatistics
|
||||
} = useDrawingManager(drawingTool);
|
||||
|
||||
|
||||
@ -267,6 +283,22 @@ const flyToSelectedDrawing = () => {
|
||||
// };
|
||||
|
||||
|
||||
// 处理导入完成
|
||||
const handleImportComplete = (result: any) => {
|
||||
console.log('导入完成:', result);
|
||||
// 可以在这里添加导入完成后的处理逻辑
|
||||
if (result.success) {
|
||||
showMessage(`成功导入 ${result.importedIds.length} 个空域`, 'success');
|
||||
} else {
|
||||
showMessage('导入失败', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// 消息提示函数
|
||||
const showMessage = (message: string, type: 'success' | 'error' | 'info' = 'info') => {
|
||||
// 实现消息提示...
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// 开始跟踪相机信息
|
||||
setTimeout(() => {
|
||||
|
||||
532
src/views/cesiums/ImportToolPanel.vue
Normal file
532
src/views/cesiums/ImportToolPanel.vue
Normal file
@ -0,0 +1,532 @@
|
||||
<!-- src/components/ImportToolPanel.vue -->
|
||||
<template>
|
||||
<div class="import-tool-panel">
|
||||
<div class="panel-header">
|
||||
<h3>📥 空域导入工具</h3>
|
||||
</div>
|
||||
|
||||
<div class="panel-section">
|
||||
<h4>文件导入</h4>
|
||||
<div class="file-import">
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
@change="handleFileSelect"
|
||||
accept=".json,.geojson"
|
||||
style="display: none"
|
||||
/>
|
||||
<button @click="triggerFileInput" class="import-btn file-btn">
|
||||
📁 选择文件导入
|
||||
</button>
|
||||
<div class="file-info" v-if="selectedFile">
|
||||
<span>已选择: {{ selectedFile.name }}</span>
|
||||
<button @click="clearFile" class="clear-file-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="import-options">
|
||||
<label class="option-checkbox">
|
||||
<input type="checkbox" v-model="importOptions.autoZoom" />
|
||||
导入后自动缩放
|
||||
</label>
|
||||
<label class="option-checkbox">
|
||||
<input type="checkbox" v-model="importOptions.mergeExisting" />
|
||||
合并现有空域
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="executeFileImport"
|
||||
class="import-btn execute-btn"
|
||||
:disabled="!selectedFile || isImporting"
|
||||
>
|
||||
{{ isImporting ? '⏳ 导入中...' : '🚀 执行导入' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="panel-section">
|
||||
<h4>数据导入</h4>
|
||||
<div class="data-import">
|
||||
<textarea
|
||||
v-model="jsonInput"
|
||||
placeholder="在此粘贴JSON或GeoJSON数据..."
|
||||
class="json-input"
|
||||
rows="8"
|
||||
></textarea>
|
||||
<button
|
||||
@click="executeJsonImport"
|
||||
class="import-btn json-btn"
|
||||
:disabled="!jsonInput.trim() || isImporting"
|
||||
>
|
||||
{{ isImporting ? '⏳ 导入中...' : '📋 导入JSON数据' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-section" v-if="importResult">
|
||||
<h4>导入结果</h4>
|
||||
<div class="import-result" :class="importResult.success ? 'success' : 'error'">
|
||||
<div class="result-icon">
|
||||
{{ importResult.success ? '✅' : '❌' }}
|
||||
</div>
|
||||
<div class="result-content">
|
||||
<div class="result-message">{{ importResult.message }}</div>
|
||||
<div v-if="importResult.importedIds" class="result-details">
|
||||
导入ID: {{ importResult.importedIds.join(', ') }}
|
||||
</div>
|
||||
<div v-if="importResult.statistics" class="result-statistics">
|
||||
圆形: {{ importResult.statistics.circles }}个,
|
||||
多边形: {{ importResult.statistics.polygons }}个,
|
||||
总计: {{ importResult.statistics.total }}个
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-section">
|
||||
<h4>示例数据</h4>
|
||||
<div class="sample-data">
|
||||
<button @click="loadSampleData" class="sample-btn">
|
||||
🎯 加载示例数据
|
||||
</button>
|
||||
<div class="sample-description">
|
||||
加载示例圆形和多边形空域数据用于测试
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-section help-section">
|
||||
<h4>支持格式</h4>
|
||||
<ul class="format-list">
|
||||
<li>✅ <strong>JSON数组</strong>: 包含空域对象的数组</li>
|
||||
<li>✅ <strong>GeoJSON</strong>: 标准的GeoJSON格式</li>
|
||||
<li>✅ <strong>文件类型</strong>: .json, .geojson</li>
|
||||
</ul>
|
||||
|
||||
<h4>数据格式示例</h4>
|
||||
<pre class="format-example">{{ formatExample }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
importFromFile: (file: File, options: any) => Promise<string[]>;
|
||||
importAirspaceData: (data: any[], options: any) => string[];
|
||||
getImportStatistics: () => { total: number; circles: number; polygons: number };
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'import-complete': [result: any];
|
||||
}>();
|
||||
|
||||
const fileInput = ref<HTMLInputElement>();
|
||||
const selectedFile = ref<File | null>(null);
|
||||
const jsonInput = ref('');
|
||||
const isImporting = ref(false);
|
||||
const importResult = ref<any>(null);
|
||||
|
||||
const importOptions = ref({
|
||||
autoZoom: true,
|
||||
mergeExisting: false
|
||||
});
|
||||
|
||||
// 触发文件选择
|
||||
const triggerFileInput = () => {
|
||||
fileInput.value?.click();
|
||||
};
|
||||
|
||||
// 处理文件选择
|
||||
const handleFileSelect = (event: Event) => {
|
||||
const target = event.target as HTMLInputElement;
|
||||
if (target.files && target.files[0]) {
|
||||
selectedFile.value = target.files[0];
|
||||
}
|
||||
};
|
||||
|
||||
// 清除文件选择
|
||||
const clearFile = () => {
|
||||
selectedFile.value = null;
|
||||
if (fileInput.value) {
|
||||
fileInput.value.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 执行文件导入
|
||||
const executeFileImport = async () => {
|
||||
if (!selectedFile.value) return;
|
||||
|
||||
isImporting.value = true;
|
||||
importResult.value = null;
|
||||
|
||||
try {
|
||||
const importedIds = await props.importFromFile(selectedFile.value, importOptions.value);
|
||||
|
||||
const statistics = props.getImportStatistics();
|
||||
importResult.value = {
|
||||
success: true,
|
||||
message: `成功导入 ${importedIds.length} 个空域`,
|
||||
importedIds,
|
||||
statistics
|
||||
};
|
||||
|
||||
emit('import-complete', importResult.value);
|
||||
} catch (error) {
|
||||
importResult.value = {
|
||||
success: false,
|
||||
message: `导入失败: ${error}`
|
||||
};
|
||||
} finally {
|
||||
isImporting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 执行JSON导入
|
||||
const executeJsonImport = () => {
|
||||
if (!jsonInput.value.trim()) return;
|
||||
|
||||
isImporting.value = true;
|
||||
importResult.value = null;
|
||||
|
||||
try {
|
||||
const data = JSON.parse(jsonInput.value);
|
||||
const importedIds = props.importAirspaceData(data, importOptions.value);
|
||||
|
||||
const statistics = props.getImportStatistics();
|
||||
importResult.value = {
|
||||
success: true,
|
||||
message: `成功导入 ${importedIds.length} 个空域`,
|
||||
importedIds,
|
||||
statistics
|
||||
};
|
||||
|
||||
emit('import-complete', importResult.value);
|
||||
} catch (error) {
|
||||
importResult.value = {
|
||||
success: false,
|
||||
message: `JSON解析失败: ${error}`
|
||||
};
|
||||
} finally {
|
||||
isImporting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 加载示例数据
|
||||
const loadSampleData = () => {
|
||||
const sampleData = [
|
||||
{
|
||||
id: 'sample_circle_1',
|
||||
type: 'circle',
|
||||
coordinates: {
|
||||
center: {
|
||||
longitude: 106.5516,
|
||||
latitude: 29.5630,
|
||||
height: 500
|
||||
},
|
||||
radius: 2000
|
||||
},
|
||||
properties: {
|
||||
name: '示例圆形空域1',
|
||||
color: '#FF6B6B',
|
||||
outlineColor: '#C44D4D'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'sample_circle_2',
|
||||
type: 'circle',
|
||||
coordinates: {
|
||||
center: {
|
||||
longitude: 106.5716,
|
||||
latitude: 29.5730,
|
||||
height: 500
|
||||
},
|
||||
radius: 1500
|
||||
},
|
||||
properties: {
|
||||
name: '示例圆形空域2',
|
||||
color: '#4ECDC4',
|
||||
outlineColor: '#3AA39C'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'sample_polygon_1',
|
||||
type: 'polygon',
|
||||
coordinates: [
|
||||
{ longitude: 106.5400, latitude: 29.5500, height: 300 },
|
||||
{ longitude: 106.5600, latitude: 29.5500, height: 300 },
|
||||
{ longitude: 106.5600, latitude: 29.5700, height: 300 },
|
||||
{ longitude: 106.5400, latitude: 29.5700, height: 300 }
|
||||
],
|
||||
properties: {
|
||||
name: '示例多边形空域1',
|
||||
color: '#45B7D1',
|
||||
outlineColor: '#368EA6'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
jsonInput.value = JSON.stringify(sampleData, null, 2);
|
||||
};
|
||||
|
||||
// 格式示例
|
||||
const formatExample = computed(() => {
|
||||
return `{
|
||||
"id": "airspace_001",
|
||||
"type": "circle",
|
||||
"coordinates": {
|
||||
"center": {
|
||||
"longitude": 106.5516,
|
||||
"latitude": 29.5630,
|
||||
"height": 500
|
||||
},
|
||||
"radius": 2000
|
||||
},
|
||||
"properties": {
|
||||
"name": "禁飞区",
|
||||
"color": "#FF0000"
|
||||
}
|
||||
}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.import-tool-panel {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 220px;
|
||||
background: rgba(42, 42, 42, 0.95);
|
||||
color: white;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
min-width: 320px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.panel-header h3 {
|
||||
margin: 0 0 15px 0;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.panel-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.panel-section h4 {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.file-import {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.import-btn {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.import-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.import-btn:not(:disabled):hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.file-btn {
|
||||
background: rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
.file-btn:hover:not(:disabled) {
|
||||
background: rgba(76, 175, 80, 0.5);
|
||||
}
|
||||
|
||||
.json-btn {
|
||||
background: rgba(156, 39, 176, 0.3);
|
||||
}
|
||||
|
||||
.json-btn:hover:not(:disabled) {
|
||||
background: rgba(156, 39, 176, 0.5);
|
||||
}
|
||||
|
||||
.execute-btn {
|
||||
background: rgba(255, 152, 0, 0.3);
|
||||
}
|
||||
|
||||
.execute-btn:hover:not(:disabled) {
|
||||
background: rgba(255, 152, 0, 0.5);
|
||||
}
|
||||
|
||||
.file-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.clear-file-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #ff4d4f;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.import-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.option-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.option-checkbox input {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.data-import {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.json-input {
|
||||
padding: 10px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.json-input::placeholder {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.import-result {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.import-result.success {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border: 1px solid rgba(76, 175, 80, 0.4);
|
||||
}
|
||||
|
||||
.import-result.error {
|
||||
background: rgba(244, 67, 54, 0.2);
|
||||
border: 1px solid rgba(244, 67, 54, 0.4);
|
||||
}
|
||||
|
||||
.result-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.result-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.result-message {
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.result-details,
|
||||
.result-statistics {
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.sample-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.sample-btn {
|
||||
padding: 8px 12px;
|
||||
background: rgba(33, 150, 243, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sample-btn:hover {
|
||||
background: rgba(33, 150, 243, 0.5);
|
||||
}
|
||||
|
||||
.sample-description {
|
||||
font-size: 11px;
|
||||
color: #ccc;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.help-section {
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.format-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.format-list li {
|
||||
font-size: 11px;
|
||||
margin-bottom: 6px;
|
||||
color: #ccc;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.format-list strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.format-example {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
color: #fff;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
@ -19,7 +19,11 @@ export interface PolygonOptions extends DrawingOptions {
|
||||
closePath?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export interface ImportOptions {
|
||||
autoZoom?: boolean;
|
||||
mergeExisting?: boolean;
|
||||
coordinateSystem?: 'wgs84' | 'gcj02' | 'bd09';
|
||||
}
|
||||
|
||||
export interface DrawingInfo {
|
||||
id: string;
|
||||
@ -64,7 +68,7 @@ export class DrawingTool {
|
||||
fill: true,
|
||||
classificationType: Cesium.ClassificationType.BOTH,
|
||||
height: 0,
|
||||
extrudedHeight: 1000
|
||||
extrudedHeight: 0
|
||||
};
|
||||
|
||||
private drawingCallbacks: {
|
||||
@ -668,6 +672,467 @@ export class DrawingTool {
|
||||
// }
|
||||
|
||||
|
||||
/**
|
||||
* 导入空域数据
|
||||
*/
|
||||
importAirspaceData(data: any[], options: ImportOptions = {}): string[] {
|
||||
console.log('=== 开始导入空域数据 ===');
|
||||
console.log('导入数据:', data);
|
||||
console.log('导入选项:', options);
|
||||
|
||||
const defaultOptions: ImportOptions = {
|
||||
autoZoom: true,
|
||||
mergeExisting: false,
|
||||
coordinateSystem: 'wgs84'
|
||||
};
|
||||
const mergedOptions = { ...defaultOptions, ...options };
|
||||
|
||||
const importedIds: string[] = [];
|
||||
|
||||
try {
|
||||
// 如果不合并现有数据,先清除所有
|
||||
if (!mergedOptions.mergeExisting) {
|
||||
this.clearAllDrawings();
|
||||
}
|
||||
|
||||
// 处理每个空域数据
|
||||
data.forEach((item, index) => {
|
||||
try {
|
||||
const entity = this.importSingleAirspace(item, mergedOptions);
|
||||
if (entity) {
|
||||
importedIds.push(item.id || `imported_${Date.now()}_${index}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`导入第 ${index + 1} 个空域时发生错误:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`✅ 成功导入 ${importedIds.length} 个空域`);
|
||||
|
||||
// 自动缩放显示所有导入的空域
|
||||
if (mergedOptions.autoZoom && importedIds.length > 0) {
|
||||
setTimeout(() => {
|
||||
this.zoomToImportedAirspaces(importedIds);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return importedIds;
|
||||
} catch (error) {
|
||||
console.error('❌ 导入空域数据时发生错误:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入单个空域
|
||||
*/
|
||||
private importSingleAirspace(data: any, options: ImportOptions): Cesium.Entity | null {
|
||||
try {
|
||||
// 验证数据
|
||||
if (!this.validateAirspaceData(data)) {
|
||||
console.error('空域数据验证失败:', data);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { type, coordinates, properties = {} } = data;
|
||||
const id = data.id || `imported_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
console.log(`导入空域 ${id}, 类型: ${type}`);
|
||||
|
||||
let positions: Cesium.Cartesian3[];
|
||||
let entity: Cesium.Entity;
|
||||
|
||||
if (type === 'circle') {
|
||||
entity = this.importCircleAirspace(id, coordinates, properties, options);
|
||||
} else if (type === 'polygon') {
|
||||
entity = this.importPolygonAirspace(id, coordinates, properties, options);
|
||||
} else {
|
||||
console.error('不支持的空域类型:', type);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entity) {
|
||||
// 生成绘图信息
|
||||
const drawingInfo = this.generateDrawingInfo({
|
||||
id,
|
||||
type: type as 'circle' | 'polygon',
|
||||
positions,
|
||||
entity,
|
||||
properties: {
|
||||
...properties,
|
||||
options: properties.options || this.defaultOptions,
|
||||
area: properties.area || 0
|
||||
}
|
||||
} as DrawingResult);
|
||||
|
||||
// 保存到绘图实体集合
|
||||
const drawingResult: DrawingResult = {
|
||||
id,
|
||||
type: type as 'circle' | 'polygon',
|
||||
positions,
|
||||
entity,
|
||||
properties: {
|
||||
...properties,
|
||||
imported: true, // 标记为导入的空域
|
||||
importTime: new Date().toISOString()
|
||||
},
|
||||
info: {
|
||||
...drawingInfo,
|
||||
area: properties.area || this.calculateArea(positions, type as 'circle' | 'polygon')
|
||||
}
|
||||
};
|
||||
|
||||
this.drawingEntities.set(id, drawingResult);
|
||||
console.log(`✅ 成功导入空域: ${id}`);
|
||||
}
|
||||
|
||||
return entity;
|
||||
} catch (error) {
|
||||
console.error('导入单个空域时发生错误:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入圆形空域
|
||||
*/
|
||||
private importCircleAirspace(
|
||||
id: string,
|
||||
coordinates: any,
|
||||
properties: any,
|
||||
options: ImportOptions
|
||||
): Cesium.Entity {
|
||||
const { center, radius } = coordinates;
|
||||
|
||||
// 坐标转换
|
||||
const convertedCenter = this.convertCoordinates(
|
||||
center.longitude,
|
||||
center.latitude,
|
||||
center.height || 0,
|
||||
options.coordinateSystem
|
||||
);
|
||||
|
||||
const centerCartesian = Cesium.Cartesian3.fromDegrees(
|
||||
convertedCenter.longitude,
|
||||
convertedCenter.latitude,
|
||||
convertedCenter.height
|
||||
);
|
||||
|
||||
const positions = [centerCartesian];
|
||||
if (radius > 0) {
|
||||
const edgePoint = this.calculateCircleEdgePoint(convertedCenter, radius);
|
||||
positions.push(edgePoint);
|
||||
}
|
||||
|
||||
const entityOptions = {
|
||||
color: properties.color ? Cesium.Color.fromCssColorString(properties.color) : this.defaultOptions.color,
|
||||
outlineColor: properties.outlineColor ? Cesium.Color.fromCssColorString(properties.outlineColor) : this.defaultOptions.outlineColor,
|
||||
outlineWidth: properties.outlineWidth || this.defaultOptions.outlineWidth,
|
||||
height: properties.height || this.defaultOptions.height,
|
||||
extrudedHeight: properties.extrudedHeight || this.defaultOptions.extrudedHeight
|
||||
};
|
||||
|
||||
return this.createCircleEntity(id, positions, entityOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入多边形空域
|
||||
*/
|
||||
private importPolygonAirspace(
|
||||
id: string,
|
||||
coordinates: any,
|
||||
properties: any,
|
||||
options: ImportOptions
|
||||
): Cesium.Entity {
|
||||
const positions = coordinates.map((coord: any) => {
|
||||
const convertedCoord = this.convertCoordinates(
|
||||
coord.longitude,
|
||||
coord.latitude,
|
||||
coord.height || 0,
|
||||
options.coordinateSystem
|
||||
);
|
||||
|
||||
return Cesium.Cartesian3.fromDegrees(
|
||||
convertedCoord.longitude,
|
||||
convertedCoord.latitude,
|
||||
convertedCoord.height
|
||||
);
|
||||
});
|
||||
|
||||
const entityOptions = {
|
||||
color: properties.color ? Cesium.Color.fromCssColorString(properties.color) : this.defaultOptions.color,
|
||||
outlineColor: properties.outlineColor ? Cesium.Color.fromCssColorString(properties.outlineColor) : this.defaultOptions.outlineColor,
|
||||
outlineWidth: properties.outlineWidth || this.defaultOptions.outlineWidth,
|
||||
height: properties.height || this.defaultOptions.height,
|
||||
extrudedHeight: properties.extrudedHeight || this.defaultOptions.extrudedHeight
|
||||
};
|
||||
|
||||
return this.createPolygonEntity(id, positions, entityOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证空域数据
|
||||
*/
|
||||
private validateAirspaceData(data: any): boolean {
|
||||
if (!data) {
|
||||
console.error('空域数据为空');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data.type) {
|
||||
console.error('空域类型未定义');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data.coordinates) {
|
||||
console.error('空域坐标未定义');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.type === 'circle') {
|
||||
if (!data.coordinates.center || !data.coordinates.radius) {
|
||||
console.error('圆形空域缺少中心点或半径');
|
||||
return false;
|
||||
}
|
||||
} else if (data.type === 'polygon') {
|
||||
if (!Array.isArray(data.coordinates) || data.coordinates.length < 3) {
|
||||
console.error('多边形空域坐标无效');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
console.error('不支持的空域类型:', data.type);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 坐标转换(支持不同坐标系)
|
||||
*/
|
||||
private convertCoordinates(
|
||||
longitude: number,
|
||||
latitude: number,
|
||||
height: number,
|
||||
coordinateSystem: string = 'wgs84'
|
||||
): { longitude: number; latitude: number; height: number } {
|
||||
// 这里可以添加坐标转换逻辑
|
||||
// 目前只支持WGS84,可以扩展支持GCJ02、BD09等
|
||||
switch (coordinateSystem) {
|
||||
case 'wgs84':
|
||||
return { longitude, latitude, height };
|
||||
case 'gcj02':
|
||||
// 这里可以添加GCJ02到WGS84的转换
|
||||
console.warn('GCJ02坐标转换暂未实现,使用原始坐标');
|
||||
return { longitude, latitude, height };
|
||||
case 'bd09':
|
||||
// 这里可以添加BD09到WGS84的转换
|
||||
console.warn('BD09坐标转换暂未实现,使用原始坐标');
|
||||
return { longitude, latitude, height };
|
||||
default:
|
||||
console.warn(`不支持的坐标系: ${coordinateSystem},使用WGS84`);
|
||||
return { longitude, latitude, height };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算圆形边缘点(用于确定半径)
|
||||
*/
|
||||
private calculateCircleEdgePoint(center: any, radius: number): Cesium.Cartesian3 {
|
||||
const earthRadius = 6371000; // 地球半径(米)
|
||||
const angularDistance = radius / earthRadius;
|
||||
|
||||
// 在正北方向计算边缘点
|
||||
const edgeLat = center.latitude + (angularDistance * 180 / Math.PI);
|
||||
|
||||
return Cesium.Cartesian3.fromDegrees(
|
||||
center.longitude,
|
||||
edgeLat,
|
||||
center.height
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 缩放显示导入的空域
|
||||
*/
|
||||
private zoomToImportedAirspaces(importedIds: string[]): void {
|
||||
try {
|
||||
const entities: Cesium.Entity[] = [];
|
||||
|
||||
importedIds.forEach(id => {
|
||||
const drawing = this.drawingEntities.get(id);
|
||||
if (drawing) {
|
||||
entities.push(drawing.entity);
|
||||
}
|
||||
});
|
||||
|
||||
if (entities.length > 0) {
|
||||
this.viewer.zoomTo(entities, new Cesium.HeadingPitchRange(0, -Math.PI/4, 0));
|
||||
console.log('✅ 已缩放显示导入的空域');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('缩放显示导入空域时发生错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从GeoJSON导入空域
|
||||
*/
|
||||
importFromGeoJSON(geoJSON: any, options: ImportOptions = {}): string[] {
|
||||
console.log('=== 从GeoJSON导入空域 ===');
|
||||
|
||||
try {
|
||||
if (!geoJSON || !geoJSON.features) {
|
||||
console.error('无效的GeoJSON数据');
|
||||
return [];
|
||||
}
|
||||
|
||||
const airspaceData: any[] = [];
|
||||
|
||||
geoJSON.features.forEach((feature: any, index: number) => {
|
||||
try {
|
||||
const airspace = this.convertGeoJSONToAirspace(feature, index);
|
||||
if (airspace) {
|
||||
airspaceData.push(airspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`转换GeoJSON要素 ${index} 时发生错误:`, error);
|
||||
}
|
||||
});
|
||||
|
||||
return this.importAirspaceData(airspaceData, options);
|
||||
} catch (error) {
|
||||
console.error('导入GeoJSON时发生错误:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将GeoJSON要素转换为空域数据
|
||||
*/
|
||||
private convertGeoJSONToAirspace(feature: any, index: number): any {
|
||||
const { geometry, properties } = feature;
|
||||
|
||||
if (!geometry || !geometry.coordinates) {
|
||||
console.error('GeoJSON要素缺少几何数据');
|
||||
return null;
|
||||
}
|
||||
|
||||
const airspace: any = {
|
||||
id: feature.id || `geojson_${Date.now()}_${index}`,
|
||||
type: '',
|
||||
coordinates: null,
|
||||
properties: properties || {}
|
||||
};
|
||||
|
||||
switch (geometry.type) {
|
||||
case 'Polygon':
|
||||
airspace.type = 'polygon';
|
||||
// GeoJSON多边形坐标是三维数组,取第一个环(外环)
|
||||
airspace.coordinates = geometry.coordinates[0].map((coord: number[]) => ({
|
||||
longitude: coord[0],
|
||||
latitude: coord[1],
|
||||
height: coord[2] || 0
|
||||
}));
|
||||
break;
|
||||
|
||||
case 'Point':
|
||||
airspace.type = 'circle';
|
||||
// 将点转换为圆形,默认半径1000米
|
||||
airspace.coordinates = {
|
||||
center: {
|
||||
longitude: geometry.coordinates[0],
|
||||
latitude: geometry.coordinates[1],
|
||||
height: geometry.coordinates[2] || 0
|
||||
},
|
||||
radius: properties?.radius || 1000
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`不支持的GeoJSON几何类型: ${geometry.type}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return airspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件导入空域
|
||||
*/
|
||||
async importFromFile(file: File, options: ImportOptions = {}): Promise<string[]> {
|
||||
return new Promise((resolve) => {
|
||||
console.log('=== 从文件导入空域 ===');
|
||||
console.log('文件:', file);
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const content = e.target?.result as string;
|
||||
let data;
|
||||
|
||||
// 根据文件类型解析
|
||||
if (file.name.endsWith('.json')) {
|
||||
data = JSON.parse(content);
|
||||
} else if (file.name.endsWith('.geojson')) {
|
||||
data = JSON.parse(content);
|
||||
resolve(this.importFromGeoJSON(data, options));
|
||||
return;
|
||||
} else {
|
||||
console.error('不支持的文件格式:', file.name);
|
||||
resolve([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断数据格式
|
||||
if (Array.isArray(data)) {
|
||||
resolve(this.importAirspaceData(data, options));
|
||||
} else if (data.features) {
|
||||
resolve(this.importFromGeoJSON(data, options));
|
||||
} else {
|
||||
console.error('无法识别的数据格式');
|
||||
resolve([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析文件时发生错误:', error);
|
||||
resolve([]);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => {
|
||||
console.error('读取文件时发生错误');
|
||||
resolve([]);
|
||||
};
|
||||
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取导入的空域统计信息
|
||||
*/
|
||||
getImportStatistics(): { total: number; circles: number; polygons: number } {
|
||||
let circles = 0;
|
||||
let polygons = 0;
|
||||
|
||||
this.drawingEntities.forEach((drawing) => {
|
||||
if (drawing.properties.imported) {
|
||||
if (drawing.type === 'circle') {
|
||||
circles++;
|
||||
} else {
|
||||
polygons++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
total: circles + polygons,
|
||||
circles,
|
||||
polygons
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印空域基本信息
|
||||
*/
|
||||
|
||||
@ -20,7 +20,7 @@ export function useDrawingManager(drawingTool: any) { // 修改参数类型为 a
|
||||
outlineWidth: 2,
|
||||
fill: true,
|
||||
height: 0,
|
||||
extrudedHeight: 1000
|
||||
extrudedHeight: 0
|
||||
},
|
||||
polygon: {
|
||||
color: Cesium.Color.CYAN.withAlpha(0.3),
|
||||
@ -28,7 +28,7 @@ export function useDrawingManager(drawingTool: any) { // 修改参数类型为 a
|
||||
outlineWidth: 2,
|
||||
fill: true,
|
||||
height: 0,
|
||||
extrudedHeight: 1000,
|
||||
extrudedHeight: 0,
|
||||
closePath: true
|
||||
}
|
||||
});
|
||||
@ -302,6 +302,61 @@ export function useDrawingManager(drawingTool: any) { // 修改参数类型为 a
|
||||
return text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 导入空域数据
|
||||
*/
|
||||
const importAirspaceData = (data: any[], options: ImportOptions = {}): string[] => {
|
||||
const tool = getTool();
|
||||
if (!tool) {
|
||||
console.error('❌ 绘图工具未初始化');
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log('开始导入空域数据,数量:', data.length);
|
||||
return tool.importAirspaceData(data, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* 从GeoJSON导入空域
|
||||
*/
|
||||
const importFromGeoJSON = (geoJSON: any, options: ImportOptions = {}): string[] => {
|
||||
const tool = getTool();
|
||||
if (!tool) {
|
||||
console.error('❌ 绘图工具未初始化');
|
||||
return [];
|
||||
}
|
||||
|
||||
return tool.importFromGeoJSON(geoJSON, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* 从文件导入空域
|
||||
*/
|
||||
const importFromFile = async (file: File, options: ImportOptions = {}): Promise<string[]> => {
|
||||
const tool = getTool();
|
||||
if (!tool) {
|
||||
console.error('❌ 绘图工具未初始化');
|
||||
return [];
|
||||
}
|
||||
|
||||
return await tool.importFromFile(file, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取导入统计
|
||||
*/
|
||||
const getImportStatistics = (): { total: number; circles: number; polygons: number } => {
|
||||
const tool = getTool();
|
||||
if (!tool) {
|
||||
return { total: 0, circles: 0, polygons: 0 };
|
||||
}
|
||||
|
||||
return tool.getImportStatistics();
|
||||
};
|
||||
|
||||
// 更新绘图选项
|
||||
const updateDrawingOptions = (type: 'circle' | 'polygon', options: Partial<DrawingOptions>): void => {
|
||||
drawingOptions[type] = { ...drawingOptions[type], ...options };
|
||||
@ -512,6 +567,10 @@ export function useDrawingManager(drawingTool: any) { // 修改参数类型为 a
|
||||
getSelectedDrawingJSON,
|
||||
getAllDrawingsJSON,
|
||||
exportSelectedDrawingAsText,
|
||||
flyToSelectedDrawing
|
||||
flyToSelectedDrawing,
|
||||
importAirspaceData,
|
||||
importFromGeoJSON,
|
||||
importFromFile,
|
||||
getImportStatistics
|
||||
};
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user