无人机管理后台前端(已迁走)
张含笑
2025-10-29 d95ca015de7641aba381e1c61de9dbdada085d4e
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
<template>
  <basic-container>
    <div class="layerContainer">
      <div>
        <el-tabs v-model="activeType" @tab-click="handleClick">
          <el-tab-pane v-for="tab in tabData" :key="tab.type" :label="tab.name" :name="tab.type">
          </el-tab-pane>
        </el-tabs>
      </div>
      <!-- 地图 -->
      <div class="mapContainer">
        <div id="layMap" class="ztzf-cesium"></div>
        <leftList
          v-if="!layerParams.addNest && !layerParams.editNest"
          @update:coverData="handleCoverDataUpdate"
          @update:deitData="handleEdit"
          :activeName="activeName"
        ></leftList>
        <rightEdit
          v-if="layerParams.addNest || layerParams.editNest"
          @callParentMethod="parentMethod"
        ></rightEdit>
      </div>
    </div>
  </basic-container>
</template>
 
<script setup>
import { flyVisual } from '@/utils/cesium/mapUtil';
import rightEdit from '@/views/layerManagement/components/rightEdit.vue';
import leftList from '@/views/layerManagement/components/leftList.vue';
import { DrawPolygon } from '@/views/layerManagement/components/utils';
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import _, { cloneDeep, throttle } from 'lodash';
import { ElButton } from 'element-plus';
import { provide } from 'vue';
const activeName = ref('电子围栏');
const activeType = ref('0');
// 当前面位置信息
let curPolygonPosition = [];
const tabData = ref([
  {
    name: '电子围栏',
    type: '0',
  },
  {
    name: '自定义禁飞区',
    type: '1',
  },
  {
    name: '国土空间规划',
    type: '2',
  },
]);
const layerParams = ref({
  addNest: false,
  editNest: false,
  editDetailData: null,
  total_count: 0,
  total_area: 0,
});
provide('layerParams', layerParams);
const handleClick = tab => {
  const clickedTab = tabData.value.find(item => item.type === tab.paneName);
  activeType.value = clickedTab.type;
  activeName.value = clickedTab.name;
};
 
// 解析 geo_data 并转换为坐标格式(兼容 Polygon 和 LineString)
const parseGeoDataToPositions = (geoDataStr, altitude) => {
  try {
    const geoData = JSON.parse(geoDataStr);
    let coordinates = [];
 
    if (geoData.type === 'Polygon') {
      // Polygon 坐标格式:[[[lng, lat], ...]] → 取第一个多边形的顶点
      coordinates = geoData.coordinates[0] || [];
    } else if (geoData.type === 'LineString') {
      // LineString 坐标格式:[[lng, lat], ...] → 直接取顶点
      coordinates = geoData.coordinates || [];
    } else {
      console.warn(`不支持的地理数据类型:${geoData.type}`);
      return [];
    }
 
    // 转换为 {lng, lat, height} 格式
    return coordinates.map(([lng, lat]) => ({
      lng: Number(lng),
      lat: Number(lat),
      height: Number(altitude) || 0,
    }));
  } catch (error) {
    console.error('解析 geo_data 失败:', error);
    return [];
  }
};
let tbJwdList = [];
const selectDataList = ref([]);
// 编辑区域
const handleEdit = val => {
  console.log('编辑数据', val);
  layerParams.value.editDetailData = val;
  drawPolygonExample.editThePatch(true);
  const positions = parseGeoDataToPositions(val.geo_data, val.altitude);
  if (positions.length < 3) return; // 少于3个点无法构成多边形
  drawPolygonExample.initPolygon(viewer, positions, true);
};
// 清除地图数据
const parentMethod = () => {
drawPolygonExample.delPolygon()
};
// 点击显示区域
const handleCoverDataUpdate = data => {
  selectDataList.value = data;
 
  if (selectDataList.value.length > 0) {
    loadDataToMap(selectDataList.value);
  } else {
    viewer.entities.removeAll();
  }
};
const loadDataToMap = dataList => {
  if (!viewer) return;
  viewer.entities.removeAll();
  // 存储所有有效坐标,用于后续定位
  tbJwdList = [];
  dataList.forEach(item => {
    const positions = parseGeoDataToPositions(item.geo_data, item.altitude);
    if (positions.length < 3) {
      console.warn(`数据 ${item.name} 坐标点不足,无法绘制`);
      return;
    }
 
    tbJwdList.push(...positions);
    //  转换为 Cesium 所需的 [lng, lat, lng, lat, ...] 扁平数组格式
    let degreesArray = [];
    positions.forEach(pos => {
      degreesArray.push(pos.lng, pos.lat, pos.height);
    });
    viewer.entities.add({
      id: `polygon_${item.id}`,
      customType: 'fence_polygon',
      customInfo: item,
      polygon: {
        hierarchy: new Cesium.PolygonHierarchy(
          Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray)
        ),
        material: Cesium.Color.YELLOW.withAlpha(0.5), // 填充色
        outline: true, // 显示边框
        outlineColor: Cesium.Color.YELLOW, // 边框色
        outlineWidth: 2, // 边框宽度
        clampToGround: true, // 贴地显示
      },
 
      polyline: {
        positions: Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray),
        width: 2,
        material: Cesium.Color.YELLOW,
        clampToGround: true,
      },
      zIndex: 99, // 层级,确保在其他图层上方显示
    });
  });
  focusOnAllFeatures();
  //  添加点击事件(参考 entitiesAddSpot 的 spotHighlighting 逻辑)
  // 移除已有点击事件,避免重复绑定
  // viewInstance.value?.removeLeftClickEvent('fenceHighlighting');
  // 绑定新的点击事件,用于高亮选中的围栏
  // viewInstance.value?.addLeftClickEvent(null, handleFenceClick, 'fenceHighlighting');
};
//计算所有图斑的包围球并定位
const focusOnAllFeatures = () => {
  if (tbJwdList.length === 0 || !viewer) return;
 
  // 转换所有经纬度坐标为Cartesian3
  const allPositions = tbJwdList.flatMap(pos =>
    Cesium.Cartesian3.fromDegrees(pos.lng, pos.lat, pos.height || 0)
  );
  if (allPositions.length === 0) return;
  const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions);
  viewer.camera.flyToBoundingSphere(boundingSphere, {
    duration: 0, // 飞行时间(秒)
    offset: new Cesium.HeadingPitchRange(
      Cesium.Math.toRadians(0), // 方向角
      Cesium.Math.toRadians(-90) // 俯仰角
    ),
  });
};
// 围栏点击处理函数(高亮选中项 + 可联动右侧编辑组件)
const handleFenceClick = movement => {
  if (!viewer) return;
 
  // 拾取点击的实体
  const pickedObject = viewer.scene.pick(movement.position);
  if (Cesium.defined(pickedObject) && pickedObject.id?.customType === 'fence_polygon') {
    const selectedEntity = pickedObject.id;
    console.log('选中的围栏数据:', selectedEntity.customInfo);
 
    // 1. 高亮处理(清除其他实体高亮,仅高亮当前选中)
    viewer.entities.values.forEach(entity => {
      if (entity.customType === 'fence_polygon') {
        // 恢复默认样式
        entity.polygon.material =
          entity === selectedEntity
            ? selectedEntity.polygon.material.color.withAlpha(0.8) // 选中时加深透明度
            : selectedEntity.polygon.material.color.withAlpha(0.5); // 未选中恢复默认
      }
    });
 
    // 2. 联动右侧编辑组件(假设 rightEdit 有接收数据的方法)
    // 例如:通过 ref 调用 rightEdit 的 setData 方法
    // rightEditRef.value?.setData(selectedEntity.customInfo);
  }
};
let publicCesiumInstance = null;
let viewer = null;
const viewInstance = shallowRef(null);
const homeViewer = shallowRef(null);
 
// 初始化绘制工具实例
const drawPolygonExample = new DrawPolygon();
 
// 地图初始化
const initMap = () => {
  publicCesiumInstance = new PublicCesium({
    dom: 'layMap',
    flatMode: false,
    terrain: true,
    layerMode: 4,
    contour: false,
  });
 
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  // drawPolygonExample.initHandler(viewer);
};
 
// 编辑/绘制
const loadPlanarRoute = async (positions = null, save = false) => {
  if (positions) {
    curPolygonPosition = positions.map(item => {
      let cartographic = Cesium.Cartographic.fromCartesian(item);
      let lng = Cesium.Math.toDegrees(cartographic.longitude); // 经度
      let lat = Cesium.Math.toDegrees(cartographic.latitude); // 纬度
      let height = Cesium.Math.toDegrees(cartographic.height); //高度
      return {
        lng: _.round(lng, 6),
        lat: _.round(lat, 6),
        height: _.round(height, 6),
      };
    });
  }
  let polygon = curPolygonPosition.map(item => [item?.lng, item?.lat]);
  polygon.push([curPolygonPosition[0]?.lng, curPolygonPosition[0]?.lat]);
  console.log('绘制的多边形顶点坐标(Cartesian3):', polygon);
 
  console.log('转换后的经纬度坐标:', curPolygonPosition);
};
const throttleLoadPlanarRoute = throttle(loadPlanarRoute, 200);
drawPolygonExample.subscribe('getPolygonPositions', data => {
  throttleLoadPlanarRoute(data);
});
 
// 地图销毁
const destroyMap = () => {
  drawPolygonExample.destroy();
 
  if (viewer) {
    viewer.destroy();
    viewer = null;
  }
  publicCesiumInstance = null;
};
// 阻止浏览器默认
const preventDefault = event => {
  event.preventDefault();
  return;
};
const cesiumContextMenu = (isAdd = true) => {
  let cesium = document.getElementById('layMap');
  if (!cesium) return;
  if (isAdd) {
    cesium.addEventListener('contextmenu', preventDefault);
  } else {
    cesium.removeEventListener('contextmenu', preventDefault);
  }
};
onMounted(() => {
  initMap();
  cesiumContextMenu();
});
 
onBeforeUnmount(() => {
  destroyMap();
  cesiumContextMenu(false);
});
</script>
 
<style scoped lang="scss">
.layerContainer {
  width: 100%;
  height: 90vh;
}
.mapContainer {
  position: relative;
  width: 100%;
  height: 80vh;
  #layMap {
    width: 100%;
    height: 100%;
  }
}
</style>