import * as Cesium from 'cesium'; import aggregationImg from '@/assets/images/home/useUavHome/aggregation.png'; import uavImg from '@/assets/images/home/useUavHome/uavImg.png'; import eventImg from '@/assets/images/home/useEventOperate/event.png'; import DevicePopUpBox from '@/views/Home/useMapAggregation/DevicePopUpBox.vue'; import EventPopUpBox from '@/views/Home/useMapAggregation/EventPopUpBox.vue'; import { render } from 'vue'; import { useStore } from 'vuex'; import { getCenterPoint } from '@/utils/cesium/mapUtil'; import cesiumOperation from '@/utils/cesium-tsa'; import { getDeviceRegion, getDeviceRegionCount, getEventDetails, getMapEvents } from '@/api/home/aggregation'; /** * 机巢聚合功能 */ export const useMapAggregation = type => { const { flyTo } = cesiumOperation(); const singleImg = type === 'device' ? uavImg : eventImg; const mergeImg = type === 'device' ? aggregationImg : aggregationImg; const MapPopUpBox = type === 'device' ? DevicePopUpBox : EventPopUpBox; const styleTransform = type === 'device' ? 'translateY(-50%)' : 'translate(-50%,-110%)'; let scalingJudgment = [ { name: '县', splashedList: [], gJson: null, show: false, outline: {}, value: [0, 48651], height: 31753 }, { name: '市', splashedList: [], gJson: null, show: false, outline: {}, value: [48651, 314863], height: 257731 }, { name: '省', splashedList: [], gJson: null, show: false, outline: {}, value: [314863, 3796280000], height: 1987280, }, ]; let viewer = null; let active = null; let handler = null; let positionC3 = null; let currentEntity = null; const store = useStore(); const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode); const selectedAreaCode = computed(() => store.state.user.selectedAreaCode); // 确定缩放比例 const determineScaling = () => { // console.log('确定缩放比例'); if (!viewer) return; let height = viewer.camera.positionCartographic.height; // 根据高度展示对应的 gJson for (let [index, item] of scalingJudgment.entries()) { if (!item.show) return; if (height > item.value[0] && height <= item.value[1]) { if (active === item.name) return; active = item.name; removeEntities(); removeLabel(); renderOutline(item); if (!item.gJson && !item.splashedList?.length) return; item.gJson ? aggregation(item) : splashed(item); break; } } }; // 转换为目录结构, 返回数组, function convertToHierarchy(code) { const codeStr = code.toString(); const provinceCode = codeStr.slice(0, 2) + '0000'; const cityCode = codeStr.slice(0, 4) + '00'; if (codeStr.slice(2, 4) === '00' && codeStr.slice(4, 6) === '00') { return [provinceCode]; } else if (codeStr.slice(4, 6) === '00') { return [provinceCode, cityCode]; } return [provinceCode, cityCode, code]; } // 获取设备聚合 function getDeviceCount(areaCode) { return getDeviceRegionCount({ areaCode }).then(res => { return (res?.data?.data || []) }); } // 获取设备散点 function getDeviceList(areaCode) { return getDeviceRegion({ areaCode }).then(res => { return (res?.data?.data || []).map(i =>({...i,type})); }); } // 事件散点 let eventList = [] function processChildren(childrens) { return (childrens || []).map(item => { const arr = processChildren(item.childrens); if (item.data){ eventList = eventList.concat(item.data) } const returnObj = { total_device_count: item.number, region_code: item.id, region_name: item.name, }; if (arr.length !== 0) { returnObj.childrens = arr; } return returnObj; }); } // 获取事件聚合 function getMapEventCount(areaCode) { return getMapEvents({ areaCode }).then(res => { const resData= res?.data?.data if (resData?.data){ eventList = resData?.data return [] } return processChildren(resData?.childrens); }); } const findFun = (featItem, numItem) => Number(featItem.region_code.slice(0, 6)) === numItem.properties.adcode; const { VITE_APP_BASE,VITE_APP_ENV } = import.meta.env; const defaultDir = `${VITE_APP_BASE}${VITE_APP_ENV === 'development' ? 'public/' : ''}geoJson/100000/`; const getFiler = async url => { const res = await fetch(url); return await res.json() }; const getOutLine = async (jsonPathPre, hierarchy) => { const parentGJson = await getFiler(`${defaultDir}${jsonPathPre}/index.json`); let features = parentGJson.features.find( item => item.properties.adcode === Number(hierarchy[hierarchy.length - 1]) ); return { type: 'FeatureCollection', features: [features] }; }; const injectData = (gJson, dataList) => { return { ...gJson, features: gJson.features.map(item => { const findData = dataList.find(item1 => findFun(item1, item)) return { ...item, data: {...findData,type: type + 'Aggregation'} } }), }; }; const initMapData = async areaCode => { eventList = [] if (!areaCode) return; const list = type === 'device' ? await getDeviceCount(areaCode) : await getMapEventCount(areaCode); const splashedList = type === 'device' ? await getDeviceList(areaCode) : eventList.map(i=>({eventId:i.id,latitude:Number(i.latitude),longitude:Number(i.longitude),type:'event'})) console.log(list, 'list'); console.log(splashedList, 'splashedList'); const hierarchy = convertToHierarchy(areaCode.slice(0, 6)); const jsonPath = hierarchy.join('/'); const jsonPathPre = hierarchy.slice(0, hierarchy.length - 1).join('/'); scalingJudgment = scalingJudgment.map(item => ({ ...item, show: true })); scalingJudgment[0].gJson = null; scalingJudgment[0].splashedList = splashedList; active = null; // 省 if (hierarchy.length === 1) { const gJson1 = await getFiler(`${defaultDir}${jsonPath}/indexDistrict.json`); const gJson2 = await getFiler(`${defaultDir}${jsonPath}/index.json`); scalingJudgment[1].gJson = { ...gJson1, features: gJson1.features.map(item => { const findData = list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item)) return { ...item, data: {...findData,type: type + 'Aggregation'} } }), }; scalingJudgment[2].gJson = injectData(gJson2, list); } // 市 if (hierarchy.length === 2) { scalingJudgment[2].gJson = null; scalingJudgment[2].show = false; const gJson1 = await getFiler(`${defaultDir}${jsonPath}/index.json`); scalingJudgment[1].gJson = injectData(gJson1, list); } // 区县 if (hierarchy.length === 3) { scalingJudgment[1].gJson = null; scalingJudgment[1].show = false; scalingJudgment[2].gJson = null; scalingJudgment[2].show = false; } // 轮廓 const outlineGJson = await getOutLine(jsonPathPre, hierarchy); scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson)); const [longitude,latitude] = outlineGJson.features[0].properties.centroid flyTo({ longitude, latitude }, 0, scalingJudgment[(hierarchy.length-3) * (-1)].height); }; watch( selectedAreaCode, async (newValue, oldValue) => { if (!viewer) return viewer.scene.postRender.removeEventListener(determineScaling) initMapData(newValue).then(()=>{ viewer.scene.postRender.addEventListener(determineScaling) }) }, { deep: true } ); //散点机巢 function splashed(row) { row.splashedList.forEach(item => { viewer.entities.add({ position: Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude), label: { // 随机整数 text: item.nickname, font: '12pt Source Han Sans CN', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -9), }, billboard: { image: new Cesium.ConstantProperty(singleImg), width: 24, height: 24, }, properties: { customData: { data: item, }, }, }); }); } // 渲染轮廓 const renderOutline = item => { item.outline && Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => { viewer.dataSources.add(dataSource); const entities = dataSource.entities.values; entities.forEach(entity => { // 隐藏多边形填充 entity.polygon.material = Cesium.Color.TRANSPARENT; entity.polygon.outline = false; // 关闭原生轮廓 // 创建独立折线作为轮廓 const positions = entity.polygon.hierarchy.getValue().positions; viewer.entities.add({ polyline: { positions: positions, width: 5, // 直接设置宽度 material: new Cesium.PolylineGlowMaterialProperty({ glowPower: 0.5, color: Cesium.Color.AQUA, }), clampToGround: true, // 贴地显示 }, }); }); }); }; // 聚合机巢 const aggregation = (item, flyTo) => { if (!item.gJson) return; const featuresList = item.gJson.features.map(item1 => { // const {lng,lat} = getCenterPoint(item1.geometry.coordinates[0][0]) return { name: item1.properties.name, position: item1.properties.centroid, data: item1.data, id: item1.region_code, } }); // 遍历特征并添加实体 featuresList.forEach(feature => { if (!feature.position) return; const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1]); viewer.entities.add({ position: position, label: { text: feature.name, font: '14pt Source Han Sans CN', fillColor: Cesium.Color.WHITE, outlineColor: Cesium.Color.BLACK, outlineWidth: 2, style: Cesium.LabelStyle.FILL_AND_OUTLINE, verticalOrigin: Cesium.VerticalOrigin.BOTTOM, pixelOffset: new Cesium.Cartesian2(0, -9), }, }); viewer.entities.add({ id: feature.id, position: position, label: { text: feature.data.total_device_count.toString(), font: '12pt Source Han Sans CN', fillColor: Cesium.Color.BLACK, style: Cesium.LabelStyle.FILL_AND_OUTLINE, eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 让label "浮" 在广告牌前面 }, billboard: { image: new Cesium.ConstantProperty(mergeImg), width: 35, height: 35, }, properties: { id: feature.id, customData: { data: feature.data, }, }, }); }); // 加载新的 GeoJSON 数据 Cesium.GeoJsonDataSource.load(item.gJson).then(dataSource => { viewer.dataSources.add(dataSource); item.dataSource = dataSource; // 保存数据源以便后续删除 // 获取数据源中的实体 const entities = dataSource.entities.values; entities.forEach(entity => { entity.polygon.material = new Cesium.ColorMaterialProperty( Cesium.Color.YELLOW.withAlpha(0) // 透明填充 ); entity.polygon.outline = new Cesium.ConstantProperty(true); // 显示边框 entity.polygon.outlineColor = new Cesium.ConstantProperty(Cesium.Color.AQUAMARINE); }); flyTo && viewer.flyTo(dataSource, { offset: new Cesium.HeadingPitchRange( 0, // heading: 0 (朝向不变) Cesium.Math.toRadians(-90), // pitch: -90° (垂直向下) 0 // range: 0 (默认距离) ), duration: 0.5, }); }); }; // 获取弹框box const getLabelDom = data => { const vNode = h(MapPopUpBox, { data, removeLabel }); const tooltipContainer = document.createElement('div'); tooltipContainer.id = 'mapPopUpBox'; tooltipContainer.style.position = 'absolute'; tooltipContainer.style.transform = styleTransform; tooltipContainer.style.pointerEvents = 'none'; document.querySelector('.page-index').append(tooltipContainer); render(vNode, tooltipContainer); return tooltipContainer; }; // 弹框位置刷新 const labelBoxRender = () => { let dom = document.querySelector('#mapPopUpBox'); if (!dom) { dom = getLabelDom(currentEntity.properties.customData._value.data); } const screenPosition = viewer.scene.cartesianToCanvasCoordinates(positionC3); if (screenPosition) { dom.style.left = `${screenPosition.x}px`; dom.style.top = `${screenPosition.y}px`; dom.style.display = 'block'; } }; // 左键单机事件 const singleMachineEvent = async click => { const pickedObject = viewer.scene.pick(click.position); if (Cesium.defined(pickedObject) && pickedObject.id) { const entity = pickedObject.id; currentEntity = entity; positionC3 = entity?.position?._value; if (!positionC3) return; removeLabel(); const customData = entity.properties.customData._value.data; console.log('customData', customData); switch (customData.type) { case 'deviceAggregation': viewer.scene.postRender.addEventListener(labelBoxRender); break; case 'device': store.commit('setSingleUavHome', { id: '123' }); break; case 'eventAggregation': break; case 'event': viewer.scene.postRender.addEventListener(labelBoxRender); break; default: break; } } }; const removeDom = () => { const dom = document.querySelector('#mapPopUpBox'); if (dom && dom.parentNode) { dom.parentNode.removeChild(dom); } }; // 移除 点 和 gjson 实体 const removeEntities = () => { viewer.dataSources?.removeAll(true); viewer.entities?.removeAll(); }; // 移除弹框标签 const removeLabel = () => { viewer.scene.postRender.removeEventListener(labelBoxRender); removeDom(); }; // 移除所有监听事件,变量置空 const removeAll = () => { removeEntities(); removeLabel(); // viewer.camera.moveEnd.removeEventListener(determineScaling); viewer.scene.postRender.removeEventListener(determineScaling) handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK); handler?.destroy(); viewer = null; active = null; handler = null; positionC3 = null; currentEntity = null; }; const init = () => { viewer = window.$viewer; viewer.scene.postRender.removeEventListener(determineScaling) initMapData(selectedAreaCode.value || userAreaCode.value).then(()=>{ viewer.scene.postRender.addEventListener(determineScaling) }) handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK); }; onBeforeUnmount(() => {}); // onMounted(() => { // nextTick(() => { // viewer = window.$viewer; // handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas); // handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK); // }); // }); return { init, removeAll }; };