| | |
| | | <template> |
| | | <div id="taskMap" /> |
| | | <div id="taskMap"></div> |
| | | </template> |
| | | <script setup> |
| | | import * as Cesium from 'cesium' |
| | |
| | | import EndPointicon from '@/assets/images/EndPointicon.png'; |
| | | import uavImg from '@/assets/images/home/useUavHome/uavImg.png' |
| | | import { getCenterPoint } from '@/utils/cesium/mapUtil' |
| | | import { getWaylineByArea } from '@/api/home/task'; |
| | | import { useStore } from 'vuex'; |
| | | |
| | | const props = defineProps(['wayLineFile']) |
| | | const store = useStore(); |
| | | const userAreaPosition = computed(() => store.state.home.userAreaPosition); |
| | | |
| | | // 声明事件 |
| | | const emit = defineEmits(['clickPosition', 'saveWayline']); |
| | | |
| | | const props = defineProps({ |
| | | wayLineFile: { |
| | | type: String, |
| | | default: '' |
| | | }, |
| | | checkedTableData: { |
| | | type: Array, |
| | | default: () => [] |
| | | }, |
| | | waylineTypeTest: { |
| | | type: Number, |
| | | default: 3 |
| | | } |
| | | }) |
| | | |
| | | const imageryProvider_ammapSL = new Cesium.UrlTemplateImageryProvider({ |
| | | url: 'https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}', |
| | |
| | | maximumLevel: 18, |
| | | tilingScheme: new AmapMercatorTilingScheme(), |
| | | credit: 'amap_SL', |
| | | }) |
| | | let viewer = null |
| | | }); |
| | | |
| | | let viewer = null; |
| | | let currentEntity = null; |
| | | let connectLines = []; // 存储连接线实体 |
| | | let polygonPoints = []; // 存储多边形的点 |
| | | let polygonEntity = null; // 存储多边形实体 |
| | | let existingEntity = null; // 后端返回数据生成得面状线 |
| | | // 添加变量跟踪当前菜单 |
| | | let currentMenu = null; |
| | | |
| | | |
| | | const init = () => { |
| | | viewer = new Viewer('taskMap', { |
| | | terrain: Terrain.fromWorldTerrain(), |
| | |
| | | }) |
| | | viewer.imageryLayers.addImageryProvider(imageryProvider_ammapSL) |
| | | viewer.scene.morphTo2D(0); |
| | | //设置默认点 |
| | | const { longitude = 115.763819, latitude = 28.787374, height = 10 } = userAreaPosition.value || {}; |
| | | viewer.camera.setView({ |
| | | destination: Cartesian3.fromDegrees(longitude, latitude, height), |
| | | }); |
| | | } |
| | | |
| | | // 渲染线和点 |
| | | // 单点左键点击事件 |
| | | const singlePointLeftClick = () => { |
| | | viewer.screenSpaceEventHandler.setInputAction((click) => { |
| | | if (props.waylineTypeTest !== 1) return |
| | | const cartesian = viewer.camera.pickEllipsoid( |
| | | click.position, |
| | | viewer.scene.globe.ellipsoid |
| | | ); |
| | | if (cartesian) { |
| | | // 清除之前的实体 |
| | | viewer.entities.removeAll(); |
| | | |
| | | // 添加新点 |
| | | const point = viewer.entities.add({ |
| | | position: cartesian, |
| | | point: { |
| | | pixelSize: 10, |
| | | color: Cesium.Color.WHITE |
| | | } |
| | | }); |
| | | |
| | | // 转换坐标 |
| | | const cartographic = Cesium.Cartographic.fromCartesian(cartesian); |
| | | const longitude = Cesium.Math.toDegrees(cartographic.longitude); |
| | | const latitude = Cesium.Math.toDegrees(cartographic.latitude); |
| | | |
| | | // 更新当前实体引用 |
| | | currentEntity = point; |
| | | |
| | | // 发送坐标 |
| | | emit('clickPosition', { longitude, latitude }); |
| | | } |
| | | }, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
| | | }; |
| | | |
| | | // 智能规划航线 |
| | | const intelligentPlanning = () => { |
| | | // 添加点击事件监听 |
| | | viewer.screenSpaceEventHandler.setInputAction((click) => { |
| | | if (props.waylineTypeTest !== 2) return |
| | | const cartesian = viewer.camera.pickEllipsoid( |
| | | click.position, |
| | | viewer.scene.globe.ellipsoid |
| | | ); |
| | | |
| | | if (cartesian) { |
| | | // 添加新点 |
| | | const point = viewer.entities.add({ |
| | | position: cartesian, |
| | | point: { |
| | | pixelSize: 10, |
| | | color: Cesium.Color.WHITE |
| | | } |
| | | }); |
| | | |
| | | // 存储点位 |
| | | polygonPoints.push(cartesian); |
| | | |
| | | // 当点击超过2个点时绘制多边形 |
| | | if (polygonPoints.length > 2) { |
| | | // 移除旧的多边形 |
| | | if (polygonEntity) { |
| | | viewer.entities.remove(polygonEntity); |
| | | } |
| | | |
| | | // 创建新的多边形 |
| | | polygonEntity = viewer.entities.add({ |
| | | polygon: { |
| | | hierarchy: new Cesium.PolygonHierarchy(polygonPoints), |
| | | // material: new Cesium.Color.fromBytes(212, 46, 32, 100), |
| | | material: new Cesium.Color(0, 0.5, 1, 0.3), // 蓝色 |
| | | outline: true, |
| | | // outlineColor: new Cesium.Color.fromBytes(212, 46, 32, 255), |
| | | outlineColor: new Cesium.Color(0, 0.5, 1, 1), // 蓝 |
| | | outlineWidth: 2, |
| | | height: 0, // Set explicit height |
| | | heightReference: Cesium.HeightReference.NONE // Disable terrain clamping |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // 转换坐标并发送 |
| | | const cartographic = Cesium.Cartographic.fromCartesian(cartesian); |
| | | const longitude = Cesium.Math.toDegrees(cartographic.longitude); |
| | | const latitude = Cesium.Math.toDegrees(cartographic.latitude); |
| | | // emit('clickPosition', cartographic); |
| | | |
| | | } |
| | | }, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
| | | // 修改右键点击事件,添加菜单 |
| | | viewer.screenSpaceEventHandler.setInputAction((movement) => { |
| | | if (props.waylineTypeTest !== 2) return |
| | | if (polygonPoints.length > 2) { |
| | | // 清除之前的菜单 |
| | | if (currentMenu) { |
| | | document.body.querySelectorAll('.context-menu').forEach(menu => menu.remove()); |
| | | } |
| | | |
| | | const menuContainer = document.createElement('div'); |
| | | menuContainer.className = 'context-menu'; |
| | | |
| | | // 获取地图容器 |
| | | const mapContainer = document.getElementById('taskMap'); |
| | | // 使用鼠标右键点击的实际位置 |
| | | menuContainer.style.position = 'absolute'; |
| | | menuContainer.style.left = `${movement.position.x}px`; |
| | | menuContainer.style.top = `${movement.position.y}px`; |
| | | menuContainer.style.zIndex = '1000'; |
| | | |
| | | menuContainer.innerHTML = ` |
| | | <div class="menu-item" id="saveWayline">保存航线</div> |
| | | <div class="menu-item" id="cancelDraw">取消绘制</div> |
| | | `; |
| | | |
| | | mapContainer.appendChild(menuContainer); |
| | | currentMenu = menuContainer; |
| | | |
| | | // 添加全局点击事件监听 |
| | | const handleClickOutside = (e) => { |
| | | if (!menuContainer) return; |
| | | const isClickInside = menuContainer.contains(e.target); |
| | | if (!isClickInside) { |
| | | menuContainer.remove(); |
| | | document.removeEventListener('mousedown', handleClickOutside); |
| | | } |
| | | }; |
| | | |
| | | // 延迟添加事件监听,避免右键点击立即触发 |
| | | setTimeout(() => { |
| | | document.addEventListener('mousedown', handleClickOutside); |
| | | }, 100); |
| | | |
| | | // 菜单按钮点击事件 |
| | | document.getElementById('saveWayline').onclick = () => { |
| | | const coordinates = polygonPoints.map(point => { |
| | | const cartographic = Cesium.Cartographic.fromCartesian(point); |
| | | return { |
| | | longitude: Cesium.Math.toDegrees(cartographic.longitude), |
| | | latitude: Cesium.Math.toDegrees(cartographic.latitude) |
| | | }; |
| | | }); |
| | | emit('saveWayline', coordinates); |
| | | saveWaylineByArea(coordinates); |
| | | mapContainer.removeChild(menuContainer); |
| | | }; |
| | | |
| | | document.getElementById('cancelDraw').onclick = () => { |
| | | polygonPoints = []; |
| | | viewer.entities.removeAll(); |
| | | mapContainer.removeChild(menuContainer); |
| | | }; |
| | | } |
| | | }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
| | | }; |
| | | |
| | | // 保存航线并且获取线 |
| | | const saveWaylineByArea = (dataValue) => { |
| | | const polygonArray = dataValue.map(point => [point.longitude, point.latitude]); |
| | | getWaylineByArea({ type: 2, polygon: polygonArray }).then(res => { |
| | | if (res.data.code !== 0) retrun; |
| | | drawResultWayline(res.data.data); |
| | | }); |
| | | } |
| | | |
| | | // 绘制后端生成得面状线 |
| | | const drawResultWayline = (dataValue) => { |
| | | // 先检查并删除已存在的航线 |
| | | existingEntity = viewer.entities.getById('result_wayline'); |
| | | if (existingEntity) { |
| | | viewer.entities.remove(existingEntity); |
| | | } |
| | | const cartesian3List = ref([]); |
| | | dataValue.forEach((lnglat) => { |
| | | const cartesian3 = Cesium.Cartesian3.fromDegrees( |
| | | Number(lnglat.x), |
| | | Number(lnglat.y), |
| | | Number(100), // 默认100 |
| | | ) |
| | | cartesian3List.value.push(cartesian3) |
| | | }); |
| | | const setting = { |
| | | id: 'result_wayline', |
| | | polyline: { |
| | | width: 2, |
| | | positions: cartesian3List.value, |
| | | material: Cesium.Color.CHARTREUSE, |
| | | }, |
| | | } |
| | | existingEntity = viewer?.entities.add({ |
| | | polyline: setting.polyline, |
| | | id: setting.id, |
| | | }) |
| | | }; |
| | | |
| | | // 选中航线时调用 渲染线和点 type = 0 |
| | | const renderingLine = lineObj => { |
| | | const positions = lineObj.Placemark.map(item => { |
| | | const [lon, lat] = item.Point.coordinates.split(',') |
| | | return Cartesian3.fromDegrees(Number(lon), Number(lat)) |
| | | }) |
| | | // viewer.entities.add({ |
| | | // position: positions[0], |
| | | // billboard: { |
| | | // image: new Cesium.ConstantProperty(uavImg), |
| | | // width: 24, |
| | | // height: 24, |
| | | // }, |
| | | // }); |
| | | |
| | | viewer.entities.add({ |
| | | polyline: { |
| | | width: 4, |
| | |
| | | clampToGround: false, |
| | | }, |
| | | }) |
| | | console.log('positions',positions) |
| | | |
| | | positions.forEach((point, index) => { |
| | | let setting = {}; |
| | | if (index === 0) { |
| | | //TODO |
| | | } else if (index === 1) { |
| | | setting = { |
| | | position: point, |
| | | id: `point_${index}`, |
| | | billboard: { |
| | | image: Startingpointicon, |
| | | outlineWidth: 0, |
| | | width: 20, |
| | | height: 20, |
| | | scale: 1.0, |
| | | }, |
| | | } |
| | | } else if (index === positions.length - 1) { |
| | | // if (index === 0) { |
| | | // //TODO |
| | | // } |
| | | // else if (index === 1) { |
| | | // setting = { |
| | | // position: point, |
| | | // id: `point_${index}`, |
| | | // billboard: { |
| | | // image: Startingpointicon, |
| | | // outlineWidth: 0, |
| | | // width: 20, |
| | | // height: 20, |
| | | // scale: 1.0, |
| | | // }, |
| | | // } |
| | | // } else |
| | | if (index === positions.length - 1) { |
| | | setting = { |
| | | position: point, |
| | | id: `point_${index}`, |
| | |
| | | position: point, |
| | | id: `point_${index}`, |
| | | label: { |
| | | text: `${index}`, |
| | | text: `${index+1}`, |
| | | font: 'bold 14px serif', |
| | | style: Cesium.LabelStyle.FILL, |
| | | verticalOrigin: Cesium.VerticalOrigin.CENTER, // 垂直居中 |
| | |
| | | }) |
| | | } |
| | | |
| | | // const drawLine = () => { |
| | | // console.log('尽量么'); |
| | | // // props.wayLineFile.way_lines.forEach(item => { |
| | | // // let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL+ props.wayLineFile); |
| | | // let prexUrl2 = ref('https://wrj.shuixiongit.com/minio/cloud-bucket/wayline/20241224/wayline_1735032173222.kmz'); |
| | | |
| | | // analyzeKmzFile(`${prexUrl2.value}?_t=${new Date().getTime()}`).then(async res => { |
| | | // const templateXML = await res.fileInfoObj['wpmz/template.kml'] |
| | | // const templateXMLJSON = XMLToJSON(templateXML)?.['Document'] |
| | | // const templateXMLObj = removeTextKey(templateXMLJSON.Folder) |
| | | // renderingLine(templateXMLObj) |
| | | // }) |
| | | // // }) |
| | | // } |
| | | |
| | | // 异步解析kmz文件 |
| | | const analysis = async url => { |
| | | return new Promise(async resolve => { |
| | | const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`) |
| | | const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`); |
| | | const templateXML = await res.fileInfoObj['wpmz/template.kml'] |
| | | const templateXMLJSON = XMLToJSON(templateXML)?.['Document'] |
| | | const templateXMLObj = removeTextKey(templateXMLJSON.Folder) |
| | |
| | | const drawLine = async () => { |
| | | let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL+ props.wayLineFile); |
| | | const res = await analysis(prexUrl.value); |
| | | if (!res.Placemark.length) return |
| | | renderingLine(res); |
| | | const points = res.Placemark.map(item => item.Point.coordinates.split(',')) |
| | | flyToPoints(points) |
| | | } |
| | | |
| | | watch(() => props.wayLineFile, async (newValue, oldValue) => { |
| | | if(newValue){ |
| | | await removeMap(); |
| | | await init(); |
| | | // 选择航线时,根据选择机巢连线 |
| | | const selectLineFile = (newVal) => { |
| | | // 清除之前的实体 |
| | | // viewer.entities.removeAll(); |
| | | // 重新绘制航线 |
| | | // if (props.wayLineFile) { |
| | | // drawLine(); |
| | | // } |
| | | // 添加选中点和连接线 |
| | | const positions = newVal.map(item => { |
| | | const position = Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude)); |
| | | // 添加机巢点 |
| | | viewer.entities.add({ |
| | | position: position, |
| | | billboard: { |
| | | image: new Cesium.ConstantProperty(uavImg), |
| | | width: 24, |
| | | height: 24, |
| | | }, |
| | | }); |
| | | return position; |
| | | }); |
| | | |
| | | // 添加连接线 |
| | | if (positions.length > 1) { |
| | | viewer.entities.add({ |
| | | polyline: { |
| | | positions: positions, |
| | | width: 2, |
| | | material: new Cesium.PolylineDashMaterialProperty({ |
| | | color: Cesium.Color.RED, |
| | | dashLength: 8.0 |
| | | }) |
| | | } |
| | | }); |
| | | } |
| | | // 飞到中心点 |
| | | const lngLatArr = newVal.map(item => [item.longitude, item.latitude]); |
| | | flyToPoints(lngLatArr); |
| | | }; |
| | | |
| | | // 单个点生成选择多个机巢生成航线 |
| | | const singlePointLines = (newVal) => { |
| | | // 清除之前的连接线 |
| | | connectLines.forEach(line => viewer.entities.remove(line)); |
| | | connectLines = []; |
| | | |
| | | if (currentEntity) { |
| | | // 获取当前点的位置 |
| | | const currentPosition = currentEntity.position.getValue(); |
| | | |
| | | // 为每个选中的机巢创建点和连接线 |
| | | newVal.forEach(item => { |
| | | // 创建机巢点 |
| | | const nestPosition = Cartesian3.fromDegrees( |
| | | Number(item.longitude), |
| | | Number(item.latitude) |
| | | ); |
| | | viewer.entities.add({ |
| | | position: nestPosition, |
| | | billboard: { |
| | | image: new Cesium.ConstantProperty(uavImg), |
| | | width: 24, |
| | | height: 24, |
| | | }, |
| | | }); |
| | | |
| | | // 创建连接线 |
| | | const line = viewer.entities.add({ |
| | | polyline: { |
| | | positions: [currentPosition, nestPosition], |
| | | width: 2, |
| | | material: new Cesium.PolylineDashMaterialProperty({ |
| | | color: Cesium.Color.RED, |
| | | dashLength: 8.0 |
| | | }) |
| | | } |
| | | }); |
| | | connectLines.push(line); |
| | | }); |
| | | |
| | | // 飞到所有点的中心位置 |
| | | const lngLatArr = newVal.map(item => [item.longitude, item.latitude]); |
| | | flyToPoints(lngLatArr); |
| | | } |
| | | }; |
| | | |
| | | // 智慧规划航线-面状航线 |
| | | const planarPointsLines = (newVal) => { |
| | | // 如果存在面状航线 |
| | | if (existingEntity) { |
| | | const waylinePositions = existingEntity.polyline.positions.getValue(); |
| | | |
| | | newVal.forEach(item => { |
| | | // 创建机巢点 |
| | | const nestPosition = Cartesian3.fromDegrees( |
| | | Number(item.longitude), |
| | | Number(item.latitude) |
| | | ); |
| | | |
| | | // 添加机巢图标 |
| | | viewer.entities.add({ |
| | | position: nestPosition, |
| | | billboard: { |
| | | image: new Cesium.ConstantProperty(uavImg), |
| | | width: 24, |
| | | height: 24, |
| | | }, |
| | | }); |
| | | |
| | | // 找到最近的航线点并连线 |
| | | let minDistance = Number.MAX_VALUE; |
| | | let closestPosition = null; |
| | | |
| | | waylinePositions.forEach(waylinePos => { |
| | | const distance = Cartesian3.distance(nestPosition, waylinePos); |
| | | if (distance < minDistance) { |
| | | minDistance = distance; |
| | | closestPosition = waylinePos; |
| | | } |
| | | }); |
| | | |
| | | // 创建连接线 |
| | | if (closestPosition) { |
| | | const line = viewer.entities.add({ |
| | | polyline: { |
| | | positions: [nestPosition, closestPosition], |
| | | width: 2, |
| | | material: new Cesium.PolylineDashMaterialProperty({ |
| | | color: Cesium.Color.CHARTREUSE, |
| | | dashLength: 8.0 |
| | | }) |
| | | } |
| | | }); |
| | | connectLines.push(line); |
| | | } |
| | | }); |
| | | |
| | | // 飞到所有点的中心位置 |
| | | const lngLatArr = newVal.map(item => [item.longitude, item.latitude]); |
| | | flyToPoints(lngLatArr); |
| | | } |
| | | }; |
| | | |
| | | // 监听选择航线文件事件 |
| | | watch(() => props.wayLineFile, async (newVal, oldValue) => { |
| | | await removeMap(); |
| | | if(newVal){ |
| | | await drawLine(); |
| | | } |
| | | }) |
| | | }, { deep: true }); |
| | | |
| | | // 监听表格选中数据变化 |
| | | watch(() => props.checkedTableData, (newVal) => { |
| | | if (newVal.length > 0 && props.waylineTypeTest === 0) { |
| | | selectLineFile(newVal); |
| | | } else if (newVal.length > 0 && props.waylineTypeTest === 1) { |
| | | singlePointLines(newVal); |
| | | } else if (newVal.length > 0 && props.waylineTypeTest === 2) { |
| | | planarPointsLines(newVal); |
| | | } |
| | | }, { deep: true }); |
| | | |
| | | // 监听航线类型 |
| | | watch(() => props.waylineTypeTest, async (newVal) => { |
| | | await removeMap(); |
| | | if (newVal === 1) { |
| | | await singlePointLeftClick(); |
| | | } else if (newVal === 2) { |
| | | await intelligentPlanning(); |
| | | } |
| | | }, { deep: true }); |
| | | |
| | | const removeEvent = () => { |
| | | // 清除事件监听器 |
| | | viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); |
| | | viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
| | | }; |
| | | |
| | | const removeMap = () => { |
| | | viewer.entities.removeAll() |
| | | viewer.destroy() |
| | | // 清除所有实体 |
| | | if (viewer) { |
| | | // 清除连接线 |
| | | connectLines.forEach(line => viewer.entities.remove(line)); |
| | | connectLines = []; |
| | | |
| | | // 清除多边形点和实体 |
| | | polygonPoints = []; |
| | | if (polygonEntity) { |
| | | viewer.entities.remove(polygonEntity); |
| | | } |
| | | |
| | | // 清除当前实体和面状航线 |
| | | if (currentEntity) { |
| | | viewer.entities.remove(currentEntity); |
| | | } |
| | | if (existingEntity) { |
| | | viewer.entities.remove(existingEntity); |
| | | } |
| | | |
| | | viewer.entities.removeAll(); |
| | | |
| | | // 重置所有变量 |
| | | currentEntity = null; |
| | | polygonEntity = null; |
| | | existingEntity = null; |
| | | } |
| | | } |
| | | |
| | | |
| | | onBeforeUnmount(() => { |
| | | removeMap() |
| | | removeMap(); |
| | | removeEvent(); |
| | | // 移除所有实体并销毁viewer |
| | | viewer.destroy(); |
| | | viewer = null; |
| | | }) |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | init() |
| | | }) |
| | | }) |
| | | }); |
| | | </script> |
| | | <style scoped lang="scss"> |
| | | #taskMap { |
| | | position: relative; |
| | | height: 100%; |
| | | |
| | | :deep() { |
| | |
| | | display: none; |
| | | } |
| | | } |
| | | |
| | | :deep(.context-menu) { |
| | | position: absolute; |
| | | background: rgba(0, 21, 41, 0.9);; |
| | | border-radius: 4px; |
| | | padding: 8px 0; |
| | | min-width: 120px; |
| | | box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3); |
| | | |
| | | .menu-item { |
| | | padding: 8px 16px; |
| | | color: #fff; |
| | | cursor: pointer; |
| | | transition: all 0.3s; |
| | | font-size: 14px; |
| | | |
| | | &:hover { |
| | | background: rgba(255, 255, 255, 0.1); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | </style> |