<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>
|