<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" v-loading="isPageLoading"
|
element-loading-background="rgba(0, 0, 0, 0.5)"
|
element-loading-text="加载中...">
|
<div class="tool-tip warning" v-show="isShowWaringTip">
|
<span class="icon">
|
<el-icon><WarningFilled /></el-icon>
|
</span>
|
<span>{{ showWarnToolTipText }}</span>
|
</div>
|
<div class="tool-tip" v-if="!isShowWaringTip && layerParams.addNest">
|
{{ showToolTipText }}
|
</div>
|
<div id="layMap" class="ztzf-cesium"></div>
|
<leftList
|
v-if="
|
!layerParams.addNest &&
|
!layerParams.editNest &&
|
!layerParams.addFolder &&
|
!layerParams.editFolder
|
"
|
@update:coverData="handleCoverDataUpdate"
|
@update:deitData="handleEdit"
|
@update:editFolder="handleFolder"
|
:activeName="activeName"
|
@newFencesMethods="newFencesMethods"
|
:reset-check="resetCheck"
|
></leftList>
|
<rightEdit
|
v-if="layerParams.addNest || layerParams.editNest"
|
@callParentMethod="parentMethod"
|
@update:loading="handleLoadingChange"
|
></rightEdit>
|
<folderFile
|
@refreshMethod="refreshMethod"
|
:activeName="activeName"
|
@callParentMethod="parentMethod"
|
v-if="layerParams.addFolder || layerParams.editFolder"
|
></folderFile>
|
</div>
|
</div>
|
</basic-container>
|
</template>
|
|
<script setup>
|
import EventBus from '@/utils/eventBus';
|
import * as turf from '@turf/turf';
|
import { dataFolderApi } from '@/api/layer/index';
|
import folderFile from '@/views/layerManagement/components/folderFile.vue';
|
import { parseGeoDataToPositions } from '@/utils/geoParseUtil';
|
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 { provide } from 'vue';
|
import { useStore } from 'vuex';
|
const isProd = import.meta.env.VITE_APP_ENV === 'production'
|
const store = useStore();
|
const userInfo = computed(() => store.getters.userInfo);
|
const areaCode = userInfo.value?.detail?.areaCode || '';
|
const showToolTipText = ref('点击地图生成测绘区域')
|
const showWarnToolTipText = ref('测区不支持交叉面')
|
// 红色警告交叉面提示窗
|
const isShowWaringTip = ref(false);
|
const activeName = ref('自定义识别区');
|
const activeType = ref('0');
|
let tbJwdList = [];
|
const selectDataList = ref([]);
|
// 当前面位置信息
|
let curPolygonPosition = [];
|
const tabData = computed(() => {
|
const allTabs = [
|
{
|
name: '自定义识别区',
|
type: '0',
|
},
|
{
|
name: '自定义禁飞区',
|
type: '1',
|
},
|
{
|
name: '国土空间规划',
|
type: '2',
|
},
|
];
|
|
// 如果是生产环境,只返回自定义识别区
|
if (isProd) {
|
return allTabs.filter(tab => tab.type === '0');
|
}
|
return allTabs;
|
});
|
const layerParams = ref({
|
addNest: false,
|
editNest: false,
|
editDetailData: null,
|
total_count: 0,
|
total_area: 0,
|
polygonPosition: null,
|
decideWhetherToAddOrEdit: null, //新增or编辑围栏
|
addAnEditingFolder: null, //新增or编辑文件夹
|
addFolder: false,
|
editFolder: false,
|
folderOption: [], //文件夹选项
|
fenceType: 1,
|
fenceArea: 0, //围栏面积
|
fileType: 1, //文件夹类型
|
editFolderData: null,
|
editingIsProhibited: false, //禁止编辑
|
isDetailShow: false,
|
isSingleLocating:false , //是否是单个定位
|
currentLocationFolderId:null,
|
crossSurface:false,//是否交叉面
|
});
|
const resetCheck = ref(false);
|
const handleClick = tab => {
|
const clickedTab = tabData.value.find(item => item.type === tab.paneName);
|
activeType.value = clickedTab.type;
|
activeName.value = clickedTab.name;
|
getdataFolderApi();
|
parentMethod();
|
handleCoverDataUpdate([])
|
layerParams.value.addNest = false
|
layerParams.value.editNest = false
|
layerParams.value.addFolder = false
|
layerParams.value.editFolder = false
|
resetCheck.value = !resetCheck.value;
|
|
};
|
const refreshMethod = () => {
|
getdataFolderApi();
|
};
|
const getdataFolderApi = () => {
|
let id;
|
if (activeName.value === '自定义识别区') {
|
id = 1;
|
} else if (activeName.value === '自定义禁飞区') {
|
id = 2;
|
} else {
|
id = 3;
|
}
|
dataFolderApi(id).then(res => {
|
const originalFolderList = res.data.data || [];
|
const formattedFolderOption = originalFolderList.map(folder => ({
|
label: folder.name,
|
value: folder.id.toString(),
|
}));
|
layerParams.value.folderOption = formattedFolderOption;
|
});
|
};
|
|
// 编辑围栏区域
|
const handleEdit = val => {
|
viewer.entities.removeAll();
|
layerParams.value.editDetailData = val;
|
drawPolygonExample.editThePatch(true);
|
drawPolygonExample.drawTheArea(true);
|
let geoDataArr = JSON.parse(val.geo_data);
|
if (geoDataArr.length > 0) {
|
geoDataArr.pop();
|
}
|
const processedGeoData = JSON.stringify(geoDataArr);
|
//geo_data 解析坐标
|
const positions = parseGeoDataToPositions(processedGeoData, val.altitude);
|
if (positions.length < 3) return; // 少于3个点无法构成多边形
|
drawPolygonExample.initPolygon(viewer, positions, true);
|
};
|
// 编辑文件夹
|
const handleFolder = val => {
|
parentMethod()
|
selectDataList.value = val.children;
|
if (selectDataList.value?.length > 0) {
|
loadDataToMap(selectDataList.value);
|
} else {
|
viewer.entities.removeAll();
|
}
|
layerParams.value.editFolderData = val;
|
};
|
// 新增开启绘制
|
const newFencesMethods = () => {
|
viewer.entities.removeAll();
|
drawPolygonExample.initHandler(viewer);
|
drawPolygonExample.drawTheArea(true);
|
drawPolygonExample.startDrawing();
|
};
|
// 清除地图数据
|
const parentMethod = () => {
|
drawPolygonExample.delPolygon();
|
drawPolygonExample.drawTheArea(false);
|
viewer.entities.removeAll();
|
};
|
// 点击显示区域
|
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 => {
|
let positions = parseGeoDataToPositions(item.geo_data, item.altitude);
|
if (positions.length < 3) {
|
console.warn(`数据 ${item.name} 坐标点不足,无法绘制`);
|
return;
|
}
|
|
tbJwdList.push(...positions);
|
let degreesArray = [];
|
positions.forEach(pos => {
|
degreesArray.push(pos.lng, pos.lat, pos.height);
|
});
|
const colorMap = {
|
1: Cesium.Color.fromCssColorString('#00ff06').withAlpha(0.5), // 绿色(填充色)
|
2: Cesium.Color.fromCssColorString('#ff0000').withAlpha(0.5), // 红色(填充色)
|
3: Cesium.Color.fromCssColorString('#00ffea').withAlpha(0.5), // 青色(填充色)
|
// 边框色:去掉透明度,保持纯色
|
border1: Cesium.Color.fromCssColorString('#00ff06'),
|
border2: Cesium.Color.fromCssColorString('#ff0000'),
|
border3: Cesium.Color.fromCssColorString('#00ffea'),
|
};
|
const fillColor = item.category_id === 1
|
? colorMap[1]
|
: item.category_id === 2
|
? colorMap[2]
|
: item.category_id === 3
|
? colorMap[3]
|
: Cesium.Color.YELLOW.withAlpha(0.5);
|
|
const borderColor = item.category_id === 1
|
? colorMap.border1
|
: item.category_id === 2
|
? colorMap.border2
|
: item.category_id === 3
|
? colorMap.border3
|
: Cesium.Color.YELLOW;
|
viewer.entities.add({
|
id: `polygon_${item.id}`,
|
customType: 'fence_polygon',
|
customInfo: item,
|
polygon: {
|
hierarchy: new Cesium.PolygonHierarchy(
|
Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray)
|
),
|
material:fillColor, // 填充色
|
outline: true, // 显示边框
|
outlineColor: borderColor, // 边框色
|
outlineWidth: 2, // 边框宽度
|
clampToGround: true, // 贴地显示
|
},
|
|
polyline: {
|
positions: Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray),
|
width: 2,
|
material:borderColor,
|
clampToGround: true,
|
},
|
zIndex: 99,
|
});
|
});
|
if (!layerParams.value.isSingleLocating) {
|
focusOnAllFeatures();
|
}
|
viewInstance.value?.addLeftClickEvent(null, handleFenceClick);
|
};
|
const focusOnAllFeatures = () => {
|
if (tbJwdList.length === 0 || !viewer) return;
|
|
// 判断是否有指定定位的文件夹
|
if (layerParams.value.currentLocationFolderId) {
|
// 筛选当前文件夹下的所有子节点坐标
|
const targetFolderPositions = selectDataList.value
|
.filter(item => item.folder_id === layerParams.value.currentLocationFolderId)
|
.flatMap(item => {
|
const positions = parseGeoDataToPositions(item.geo_data, item.altitude);
|
return positions.length >= 3 ? positions : [];
|
});
|
|
if (targetFolderPositions.length > 0) {
|
const positionsData = targetFolderPositions.map(pos => [
|
pos.lng,
|
pos.lat,
|
pos.height || 0
|
]);
|
flyVisual({
|
positionsData,
|
viewer,
|
multiple: activeName.value === '国土空间规划' ? 13 : 10,
|
pitch: -90
|
});
|
return; // 仅聚焦当前文件夹,结束方法
|
}
|
}
|
|
// 无指定文件夹时,聚焦所有选中数据
|
const positionsData = tbJwdList.map(pos => [
|
pos.lng,
|
pos.lat,
|
pos.height || 0
|
]);
|
flyVisual({
|
positionsData,
|
viewer,
|
multiple: activeName.value === '国土空间规划' ? 13 : 4,
|
pitch: -90
|
});
|
};
|
// 区域点击显示详细信息
|
const handleFenceClick = movement => {
|
if(layerParams.value.editFolder) return
|
if (!viewer) return;
|
layerParams.value.editDetailData = null;
|
|
if(activeName.value !== '国土空间规划') {
|
const pickedObjects = viewer.scene.drillPick(movement.position, 10);
|
// 遍历所有被点击的实体,找到围栏类型(customType: 'fence_polygon')
|
let selectedEntity = null;
|
for (let i = 0; i < pickedObjects.length; i++) {
|
const pick = pickedObjects[i];
|
if (Cesium.defined(pick.id) && pick.id?.customType === 'fence_polygon') {
|
selectedEntity = pick.id;
|
break;
|
}
|
}
|
// viewer.entities.values.forEach(entity => {
|
// if (entity.customType === 'fence_polygon') {
|
// entity.polygon.material = entity === selectedEntity
|
// ? Cesium.Color.RED.withAlpha(0.5)
|
// : Cesium.Color.YELLOW.withAlpha(0.5);
|
// }
|
// });
|
if (selectedEntity) {
|
const selectedData = selectedEntity.customInfo;
|
if (activeName.value !== '国土空间规划') {
|
layerParams.value.editNest = true;
|
layerParams.value.decideWhetherToAddOrEdit = 2;
|
if (activeName.value === '自定义识别区') {
|
layerParams.value.fenceType = 1;
|
} else if (activeName.value === '自定义禁飞区') {
|
layerParams.value.fenceType = 2;
|
}
|
layerParams.value.editDetailData = selectedData;
|
layerParams.value.fenceArea = selectedData.area;
|
layerParams.value.editingIsProhibited = true;
|
}
|
}
|
}
|
|
};
|
let publicCesiumInstance = null;
|
let viewer = null;
|
const viewInstance = shallowRef(null);
|
const drawPolygonExample = new DrawPolygon();
|
// 地图初始化
|
const initMap = () => {
|
publicCesiumInstance = new PublicCesium({
|
dom: 'layMap',
|
flatMode: false,
|
terrain: true,
|
layerMode: 4, //轮廓线
|
dockOptions:{showDock:true}//机巢
|
});
|
viewer = publicCesiumInstance.getViewer();
|
viewInstance.value = publicCesiumInstance;
|
viewInstance.value?.flyToContour();
|
const nanChangPosition = Cesium.Cartesian3.fromDegrees(
|
115.892151, // 经度
|
28.676493, // 纬度
|
15000 // 相机高度
|
);
|
|
// 执行相机飞行
|
viewer.camera.flyTo({
|
destination: nanChangPosition,
|
duration: 0,
|
orientation: {
|
heading: Cesium.Math.toRadians(0),
|
pitch: Cesium.Math.toRadians(-90),
|
roll: 0,
|
},
|
});
|
};
|
// 编辑/绘制
|
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]);
|
if(polygon.length ===0){
|
isShowWaringTip.value = false
|
}
|
if(polygon.length > 0 && !isShowWaringTip.value){
|
showToolTipText.value = '测绘区域已绘制完成'
|
} else {
|
showToolTipText.value = '点击地图生成测绘区域'
|
}
|
|
if(polygon.length > 0){
|
polygon.push([curPolygonPosition[0]?.lng, curPolygonPosition[0]?.lat])
|
}
|
let polygonString = JSON.stringify(polygon);
|
layerParams.value.polygonPosition = polygon.length > 0 ? polygonString : null;
|
if (polygon.length >= 3) {
|
// 确保多边形闭合:最后一个点与第一个点一致
|
const firstPoint = polygon[0];
|
const lastPoint = polygon[polygon.length - 1];
|
const isClosed =
|
_.round(firstPoint[0], 6) === _.round(lastPoint[0], 6) &&
|
_.round(firstPoint[1], 6) === _.round(lastPoint[1], 6);
|
const closedPolygon = isClosed ? polygon : [...polygon, firstPoint];
|
const turfPolygon = turf.polygon([closedPolygon]);
|
const area = _.round(turf.area(turfPolygon), 2);
|
if (area / 1000000 < 50) {
|
layerParams.value.fenceArea = area;
|
} else {
|
layerParams.value.fenceArea = area;
|
isShowWaringTip.value = true
|
showWarnToolTipText.value = '自定义禁飞区不能超过50平方公里'
|
}
|
} else {
|
layerParams.value.fenceArea = 0;
|
}
|
};
|
drawPolygonExample.subscribe('getShowWaringTip', data => {
|
isShowWaringTip.value = data;
|
showWarnToolTipText.value = '测区不支持交叉面'
|
layerParams.value.crossSurface = data
|
});
|
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);
|
}
|
};
|
// 添加loading
|
const isPageLoading = ref(false);
|
const handleLoadingChange = (status) => {
|
isPageLoading.value = status;
|
};
|
provide('layerParams', layerParams);
|
onMounted(() => {
|
initMap();
|
cesiumContextMenu();
|
getdataFolderApi();
|
// 监听定位事件
|
EventBus.on('focusOnNode', (nodeData) => {
|
focusOnNode(nodeData);
|
});
|
EventBus.on('deleteMapEntityById', deleteMapEntityById);
|
EventBus.on('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
|
});
|
|
//定位到指定节点
|
const focusOnNode = (nodeData) => {
|
if (!viewer || !nodeData?.geo_data) return;
|
|
// 解析节点坐标
|
const positions = parseGeoDataToPositions(nodeData.geo_data, nodeData.altitude);
|
if (positions.length < 3) return;
|
|
// level 2 节点走全量聚焦,level 3 节点走单个聚焦
|
if (nodeData.level === 2) {
|
focusOnAllFeatures();
|
} else {
|
flyVisual({
|
positionsData: positions.map(pos => [pos.lng, pos.lat, pos.height || 0]),
|
viewer,
|
multiple: activeName.value === '国土空间规划' ? 13 : 7, // 缩放倍数
|
pitch: -90
|
});
|
}
|
};
|
onBeforeUnmount(() => {
|
destroyMap();
|
cesiumContextMenu(false);
|
EventBus.off('deleteMapEntityById', deleteMapEntityById);
|
EventBus.off('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
|
});
|
// 删除单个地图实体
|
const deleteMapEntityById = entityId => {
|
if (!viewer) return;
|
const entity = viewer.entities.getById(`polygon_${entityId}`);
|
if (entity) {
|
viewer.entities.remove(entity);
|
}
|
// 更新选中数据列表
|
selectDataList.value = selectDataList.value.filter(item => item.id !== entityId);
|
};
|
// 删除文件夹下所有地图实体
|
const deleteMapEntitiesByFolderId = folderId => {
|
if (!viewer) return;
|
const entitiesToDelete = viewer.entities.values.filter(entity => {
|
return entity.customInfo?.folder_id === folderId;
|
});
|
entitiesToDelete.forEach(entity => {
|
viewer.entities.remove(entity);
|
});
|
// 更新选中数据列表
|
selectDataList.value = selectDataList.value.filter(item => item.folder_id !== folderId);
|
getdataFolderApi()
|
};
|
</script>
|
|
<style scoped lang="scss">
|
.layerContainer {
|
width: 100%;
|
height: 90vh;
|
}
|
.mapContainer {
|
position: relative;
|
width: 100%;
|
height: 80vh;
|
.warning {
|
position: absolute;
|
top: 234px;
|
color: #fff;
|
background: rgba(140, 0, 0, 0.4) !important;
|
|
.icon {
|
display: flex;
|
align-items: center;
|
color: #ff1f1f;
|
}
|
}
|
#layMap {
|
width: 100%;
|
height: 100%;
|
border-radius: 8px 8px 8px 8px;
|
overflow: hidden;
|
}
|
.tool-tip {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
position: absolute;
|
width: 800px;
|
height: 64px;
|
right: 430px;
|
top: 60px;
|
font-family: Source Han Sans CN, Source Han Sans CN;
|
font-size: 18px;
|
color: #ffffff;
|
|
background: rgba(11, 31, 58, 0.7);
|
-moz-user-select: none;
|
-webkit-user-select: none;
|
-ms-user-select: none;
|
user-select: none;
|
}
|
|
}
|
</style>
|