diff --git a/index.html b/index.html index 8422405..fb8f3e1 100644 --- a/index.html +++ b/index.html @@ -24,11 +24,6 @@ cesium="true" src="public/Cesium/Cesium.js" ></script> - <!-- <script - type="text/javascript" - cesium="true" - src="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js" - ></script> --> <script type="text/javascript" cesium="true" diff --git a/package.json b/package.json index 1c3b66c..f17f7e0 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,6 @@ "scripts": { "dev": "vite", "build": "vite build", - "build-localhost": "vite build && node update-index.mjs", "preview": "vite preview", "lint": "eslint src", "fix": "eslint src --fix", diff --git a/src/assets/js/cesium-map/measureDistance.js b/src/assets/js/cesium-map/measureDistance.js index 2d226f7..98c0c67 100644 --- a/src/assets/js/cesium-map/measureDistance.js +++ b/src/assets/js/cesium-map/measureDistance.js @@ -1,6 +1,6 @@ import * as Cesium from 'cesium' import {Cartesian3} from 'cesium' -import {geojson2kml} from "@/assets/js/DataUtils.js"; +import {useStaticStore} from "@/store/staticOptions.js"; export default class MeasureDistance { constructor(viewer) { @@ -8,17 +8,18 @@ export default class MeasureDistance { this.scene = viewer.scene this.isMeasure = false this.positions = [] - this.temPositions = [] // 鼠标移动时产生的临时点 - this.vertexEntities = [] // 节点元素 - this.lineEntity = undefined // 距离测量折线元素 - this.lineEntitys = [] // 距离测量折线元素数组,每完成一次距离测量,将折线元素加入此数组 - this.totalDistance = 0 // 总距离 + this.temPositions = [] // 距离测量-鼠标移动时产生的临时点 + this.vertexEntities = [] // 距离测量-节点元素 + this.lineEntity = undefined // 距离测量-折线元素 + this.lineEntitys = [] // 距离测量-折线元素数组,每完成一次距离测量,将折线元素加入此数组 + this.totalDistance = 0 // 距离测量-总距离 - this.activeShapePoints = [] //面积测量多边形顶点 + this.activeShapePoints = [] //面积测量-多边形顶点 this.polygon = undefined this.measAreaStatus = false //是否在测量面积 - this.graphics = [] + this.analyseGraphs = [] //分析工具的图形容器 + this.graphics = [] //图形绘制容器 this.dynamicData = { lon: undefined, lat: undefined, @@ -30,15 +31,90 @@ export default class MeasureDistance { borderWidth: 1, material: Cesium.Color.GREEN.withAlpha(0.5), } + } + + profileAnalyse(){ + //解绑鼠标事件 + if(this.handler) this.unRegisterEvents() + let count = 0 + let profilePts = [] //点集合 + let line = undefined + let movePoint = undefined + this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas) + // 绑定鼠标左击事件 + this.handler.setInputAction(e => { + let position = this.viewer.scene.pickPosition(e.position); + if (!position) { + const ellipsoid = this.viewer.scene.globe.ellipsoid; + position = this.viewer.scene.camera.pickEllipsoid(e.position, ellipsoid); + } + if (!position) return; + count++ + profilePts.push(position) + let nodeEntity = this._createPointEntity(position, count.toString()) + this.viewer.entities.add(nodeEntity) + this.analyseGraphs.push(nodeEntity) + if(!line){ + line = this.viewer.entities.add({ + polyline: { + positions: new Cesium.CallbackProperty(e => { + return profilePts.concat(movePoint); + }, false), + width: 2, + material: Cesium.Color.YELLOW, + clampToGround: true, + }, + }) + } + //通视分析仅支持选择两个点 + if(useStaticStore().analysisVars.analysisType===2&&profilePts.length===2){ + this.unRegisterEvents() + useStaticStore().analysisVars.profilePts = profilePts + } + }, Cesium.ScreenSpaceEventType.LEFT_CLICK); + + this.handler.setInputAction(e => { + let position = this.viewer.scene.pickPosition(e.endPosition); + if (!position) { + position = this.viewer.scene.camera.pickEllipsoid(e.startPosition, this.viewer.scene.globe.ellipsoid); + } + if (!position) return; + movePoint = position + this.viewer.scene.requestRender() + }, Cesium.ScreenSpaceEventType.MOUSE_MOVE); + + this.handler.setInputAction(e => { + let position = this.viewer.scene.pickPosition(e.position); + if (!position) { + const ellipsoid = this.viewer.scene.globe.ellipsoid; + position = this.viewer.scene.camera.pickEllipsoid(e.position, ellipsoid); + } + if (!position) return; + count++ + profilePts.push(position) + let endEntity = this._createPointEntity(position, count.toString()) + this.viewer.entities.add(endEntity) + this.analyseGraphs.push(endEntity) + this.analyseGraphs.push(line) + this.unRegisterEvents() + useStaticStore().analysisVars.profilePts = profilePts + }, Cesium.ScreenSpaceEventType.RIGHT_CLICK); + return false } + + clearProfileEntities(){ + this.analyseGraphs.forEach((item)=>{ + this.viewer.entities.remove(item) + }) + } + updateDynamicData(data){ this.dynamicData.lon = data.lon; this.dynamicData.lat = data.lat; this.dynamicData.alt = data.alt; this.dynamicData.heading = data.heading; this.viewer.scene.requestRender() - } addKml(kml_file){ @@ -692,7 +768,7 @@ export default class MeasureDistance { }) this.graphics = [] } -/********************** 图形绘制 ***************************************/ +/********************** 实时图形绘制 ***************************************/ drawGraphics(key){ this.viewer.scene.globe.depthTestAgainstTerrain = true // 清除可能会用到的监听事件 @@ -920,4 +996,31 @@ export default class MeasureDistance { } }, Cesium.ScreenSpaceEventType.RIGHT_CLICK) } + _createPointEntity(position, label=""){ + return new Cesium.Entity({ + position: position, + point: { + pixelSize: 4, + color: Cesium.Color.RED, + outlineColor: Cesium.Color.WHITE, + outlineWidth: 1, + disableDepthTestDistance:99000000, + heightReference:Cesium.HeightReference.CLAMP_TO_GROUND, + }, + label: { + text: label, + font: "2rem sans-serif", + fillColor: Cesium.Color.WHITE, + style: Cesium.LabelStyle.FILL, + pixelOffset: new Cesium.Cartesian2(20, -10), + eyeOffset: new Cesium.Cartesian3(0, 0, 0), + horizontalOrigin: Cesium.HorizontalOrigin.CENTER, + verticalOrigin: Cesium.VerticalOrigin.BOTTOM, + scale: 0.5, + clampToGround: true, + heightReference:Cesium.HeightReference.CLAMP_TO_GROUND, + disableDepthTestDistance:99000000, + }, + }) + } } diff --git a/src/components/ProfileAnalysis.vue b/src/components/ProfileAnalysis.vue deleted file mode 100644 index 0beb8b4..0000000 --- a/src/components/ProfileAnalysis.vue +++ /dev/null @@ -1,238 +0,0 @@ -<!-- - 文件描述:地形剖面图组件 - 创建时间:2024/4/23 9:55 - 创建人:Zhaipeixiu ---> -<script setup lang="ts"> -import {ref} from 'vue' -import * as echarts from 'echarts' -const emit = defineEmits(['confirmDia']) -let showDia = ref<boolean>(false) - -const openDia = (): void => { - showDia.value = true -} - -const closeDia = (): void => { - showDia.value = false -} - -const confirmDia = (): void => { - emit('confirmDia', '弹窗内容事件处理完毕,信息传给父组件。') -} - -/** - * 绘制折线图(航线碰撞检测) - * @param xData x数组 - * @param yData y数组,以面积线绘制 - * @param yData2 y数组,以折线绘制 - */ -const drawChart_AirlineDetect = (xData:number[],yData:number[],yData2:number[]) => { - let myChart = echarts.init(document.getElementById('profileChart')) - // 绘制图表 - myChart.setOption({ - legend:{ - show: true, - type: 'plain', - top: '7%', - data:[ - { - name: 'groundLine', - itemStyle: 'inherit', - lineStyle: 'inherit', - }, - { - name: 'airLine', - itemStyle: 'inherit', - lineStyle: 'inherit', - }] - }, - tooltip: { - show: true, - trigger: 'axis', - axisPointer: { - type: 'cross' - }, - formatter:'地表高度: {c0}<br>航线高度: {c1}' - }, - xAxis: { - data: xData, - name: '距离', - nameTextStyle: { - fontWeight:'bolder', - fontSize: 14 - }, - nameLocation: 'end', - axisLine:{ - onZero: false, - show: true, // 是否显示坐标轴轴线 - symbol: ['none', 'arrow'], - symbolSize: [7, 10] - }, - axisLabel: { - formatter: '{value} m', - margin: 5, - }, - axisTick: { - show: true, // 是否显示坐标轴刻度 - inside: true, // 坐标轴刻度是否朝内,默认朝外 - alignWithLabel: true, - lineStyle: { - color: '#000000', //刻度线的颜色 - type: 'solid', //坐标轴线线的类型(solid实线类型;dashed虚线类型;dotted点状类型) - }, - } - }, - yAxis: { - type: 'value', - name: '高度', - nameTextStyle: { - fontWeight:'bolder', - fontSize: 14 - }, - nameLocation: 'end', - position: 'left', - axisLabel: { - formatter: '{value} m' - }, - axisLine: { - show: true, - symbol: ['none', 'arrow'], - symbolSize: [7, 10] - } - }, - series: [ - { - name:'groundLine', - type: 'line', - data: yData, - areaStyle: { - color: '#37a5fb', - opacity: 0.5 - } - }, - { - name:'airLine', - type: 'line', - data: yData2, - } - ] - }); -} -/** - * 绘制折线图(地形剖面) - * @param xData x数组 - * @param yData y数组,以面积线绘制 - */ -const drawChart_TerrainProfile = (xData:number[],yData:number[]) => { - let myChart = echarts.init(document.getElementById('profileChart')) - // 绘制图表 - myChart.setOption({ - legend:{ - show: true, - type: 'plain', - top: '7%', - data:[ - { - name: 'groundLine', - itemStyle: 'inherit', - lineStyle: 'inherit', - }, - { - name: 'airLine', - itemStyle: 'inherit', - lineStyle: 'inherit', - }] - }, - tooltip: { - show: true, - trigger: 'axis', - axisPointer: { - type: 'cross' - }, - formatter:'地表高度: {c0}' - }, - xAxis: { - data: xData, - name: '距离', - nameTextStyle: { - fontWeight:'bolder', - fontSize: 14 - }, - nameLocation: 'end', - axisLine:{ - onZero: false, - show: true, // 是否显示坐标轴轴线 - symbol: ['none', 'arrow'], - symbolSize: [7, 10] - }, - axisLabel: { - formatter: '{value} m', - margin: 5, - }, - axisTick: { - show: true, // 是否显示坐标轴刻度 - inside: true, // 坐标轴刻度是否朝内,默认朝外 - alignWithLabel: true, - lineStyle: { - color: '#000000', //刻度线的颜色 - type: 'solid', //坐标轴线线的类型(solid实线类型;dashed虚线类型;dotted点状类型) - }, - } - }, - yAxis: { - type: 'value', - name: '高度', - nameTextStyle: { - fontWeight:'bolder', - fontSize: 14 - }, - nameLocation: 'end', - position: 'left', - axisLabel: { - formatter: '{value} m' - }, - axisLine: { - show: true, - symbol: ['none', 'arrow'], - symbolSize: [7, 10] - } - }, - series: [ - { - name:'groundLine', - type: 'line', - data: yData, - areaStyle: { - color: '#37a5fb', - opacity: 0.5 - } - } - ] - }); -} - -// vue3中规定,使用了 <script setup> 的组件是默认私有的: -// 一个父组件无法访问到一个使用了 <script setup> 的子组件中的任何东西,除非子组件在其中通过 defineExpose 宏显式暴露 -defineExpose({ - openDia, - closeDia, - drawChart_AirlineDetect, - drawChart_TerrainProfile -}) - - -</script> - -<template> -<div id="profileChart" v-show="showDia"> -</div> -</template> - -<style scoped> -#profileChart{ - width: 700px; - height: 500px; - position: relative; -} -</style> diff --git a/src/components/SpatialAnalysis.vue b/src/components/SpatialAnalysis.vue new file mode 100644 index 0000000..a329258 --- /dev/null +++ b/src/components/SpatialAnalysis.vue @@ -0,0 +1,196 @@ +<!-- + 文件描述:地形剖面图组件 + 创建时间:2024/4/23 9:55 + 创建人:Zhaipeixiu +--> +<script setup lang="ts"> +import {nextTick, ref, watch} from "vue"; +import { + drawEchartsProfileAnalyse, + drawEchartsVisibility, elevationProfile, + profileAnalyse +} from "@/utils/map/SpatialAnalysis.ts"; +import {useStaticStore} from "@/store/staticOptions"; +import {useMessage} from "naive-ui"; +import {Cartesian3} from "cesium"; +let showResultModal = ref(false) +let store = useStaticStore() +let message = useMessage() +let showParamsDialog = ref(false) +let ifInputCoords = ref(false) +//TODO: 表单未校验 +let formParams = ref({ + interval: 100, + startClearance: 40, //起点离地高度 + endClearance: 10, //起点离地高度 +}) + +// 关闭地形分析结果窗口德回调 +const handleCloseProfile = ()=>{ + //移除图形元素 + window.measureViewer.clearProfileEntities() + //地形点数组归零 + store.analysisVars.profilePts = [] + store.analysisVars.analysisType = -1 +} + +// 执行地形分析 +function executeTopographicAnalyse(polyline:Cartesian3[]) { + // 剖面分析 + if(store.analysisVars.analysisType === 1){ + // 计算地形剖面 + let res = profileAnalyse(window.viewer, polyline,formParams.value.interval) + // 弹出图表窗口 + showResultModal.value = true + nextTick(()=>{ + drawEchartsProfileAnalyse(res.distanceArray, res.elevationArray) + }) + } + // 通视分析 + else if (store.analysisVars.analysisType === 2){ + console.log(store.analysisVars.analysisType) + // 计算地形剖面 + let res = profileAnalyse(window.viewer, polyline,formParams.value.interval) + // 计算视线起始点距离和高度 + let eyeRes = elevationProfile(window.viewer,polyline[0],polyline[1],-1) + eyeRes.elevationArray[0] += Number(formParams.value.startClearance) + eyeRes.elevationArray[1] += Number(formParams.value.endClearance) + // 弹出图表窗口 + showResultModal.value = true + nextTick(()=>{ + drawEchartsVisibility(res.distanceArray, res.elevationArray, eyeRes.elevationArray[0],eyeRes.elevationArray[1]) + }) + } + +} +watch(()=>store.analysisVars.profilePts, (newValue, OldValue): void => { + console.log("数据变化了", newValue) + if(newValue.length==1) { + message.error('请至少选择两个点') + return; + } + if(newValue.length<1) return; + executeTopographicAnalyse(newValue) +}) + +function inputCoords(){ + ifInputCoords.value = !ifInputCoords.value +} +function pickPoints() { + window.measureViewer.profileAnalyse() + showParamsDialog.value = false +} +defineExpose({ + openParamsDialog:()=>{ + showParamsDialog.value = true + } +}) + +const pickedCoords = ref([ + { + lon: null, + lat: null + } +]) +const onCreate = ()=> { + return {lon: null, lat: null} +} + +//输入坐标结束之后,进行地形分析 +const afterCoordInput = ()=> { + console.log(pickedCoords.value) + //关闭参数窗口 + showParamsDialog.value = false + + let polyline: Cartesian3[] = [] + pickedCoords.value.forEach((coord) => { + let lon1,lat1 + lon1 = Number(coord.lon) + lat1 = Number(coord.lat) + let Certain3 = Cartesian3.fromDegrees(lon1, lat1) + polyline.push(Certain3) + }) + // 执行地形分析 + executeTopographicAnalyse(polyline) +} + +</script> + +<template> + <n-modal v-model:show="showResultModal" style="width: 50rem; height: 38rem" + preset="card" draggable :mask-closable="false" :on-after-leave="handleCloseProfile" + :title="store.analysisVars.analysisType==2? '通视分析结果':'剖面分析结果'"> + <template v-slot:default> + <div id="profileEChart"></div> + </template> + </n-modal> + + <n-modal v-model:show="showParamsDialog" style="width: 20rem;" preset="card" + draggable :title="store.analysisVars.analysisType==2? '通视分析参数设置':'剖面分析参数设置'" + :mask-closable="false"> + <template v-slot:default> + <n-space> + <n-form ref="formRef" :model="formParams" + label-placement="left" label-width="auto" + require-mark-placement="right-hanging"> + <n-form-item label="采样间隔" path="numberParam" > + <n-input round v-model:value="formParams.interval"> + <template #suffix> + 米 + </template> + </n-input> + </n-form-item> + <n-form-item label="起点离地高度" path="numberParam" v-show="store.analysisVars.analysisType==2"> + <n-input round v-model:value="formParams.startClearance"> + <template #suffix> + 米 + </template> + </n-input> + </n-form-item> + <n-form-item label="终点离地高度" path="numberParam" v-show="store.analysisVars.analysisType==2"> + <n-input round v-model:value="formParams.endClearance"> + <template #suffix> + 米 + </template> + </n-input> + </n-form-item> + </n-form> + </n-space> + <n-row justify-content="space-around"> + <n-tooltip placement="bottom" trigger="hover"> + <template #trigger> + <n-button type="info" size="small" @click="pickPoints">地图选取</n-button> + </template> + <span>左键单击选点,右键结束</span> + </n-tooltip> + <n-button :type="ifInputCoords? 'warning':'info'" size="small" + @click="inputCoords">{{ifInputCoords? '取消输入':'输入坐标' }} + </n-button> + <n-button type="info" size="small">导入文件</n-button> + </n-row> + <n-divider v-show="ifInputCoords"/> + <n-dynamic-input v-model:value="pickedCoords" :on-create="onCreate" v-show="ifInputCoords" + :min="2" :max="store.analysisVars.analysisType==2? 2:10"> + <template #default="{ value }"> + <n-input size="tiny" v-model:value="value.lon" placeholder="经度"/> + <n-input size="tiny" v-model:value="value.lat" placeholder="纬度"/> + </template> + </n-dynamic-input> + <n-row justify-content="space-around" style="margin-top:.5rem;"> + <n-button type="primary" size="small" v-show="ifInputCoords" + @click="afterCoordInput" :disabled="pickedCoords.length<2"> 确定 </n-button> + </n-row> + </template> + </n-modal> + +</template> + +<style scoped> + +#profileEChart{ + width: 47rem; + height: 37rem; + position: relative; + margin-top: -2rem; +} +</style> diff --git a/src/components/UnitTest.vue b/src/components/UnitTest.vue new file mode 100644 index 0000000..745ff41 --- /dev/null +++ b/src/components/UnitTest.vue @@ -0,0 +1,22 @@ +<script lang="ts"> +import {defineComponent} from 'vue' +import {drawEchartsVisibility} from "@/utils/map/SpatialAnalysis.ts"; + +export default defineComponent({ + name: "UnitTest", + mounted() { + let xdt = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]; + let ydt = [23,34,3,4,5,6,7,8,9,23,123,122,90,45,34,16,17,142,53,2,10]; + drawEchartsVisibility(xdt, ydt, 30, 20); + } +}) +</script> + +<template> +<div id="Echarts-UnitTest" + style="width: 100%; height: 40rem;position: absolute;"></div> +</template> + +<style scoped> + +</style> \ No newline at end of file diff --git a/src/components/map/SceneViewer.vue b/src/components/map/SceneViewer.vue index f23109e..5c0c439 100644 --- a/src/components/map/SceneViewer.vue +++ b/src/components/map/SceneViewer.vue @@ -7,12 +7,11 @@ --> <template> <div id="cesium-viewer" ref="viewerDivRef"></div> - <ProfileAnalysis ref="profileChart"></ProfileAnalysis> </template> <script setup lang="ts"> -import {onMounted, ref, watch} from 'vue' -import {Viewer, Ion, CustomDataSource, Cartesian3} from 'cesium' +import {onMounted, ref} from 'vue' +import {Viewer, Ion, CustomDataSource} from 'cesium' import 'cesium/Build/Cesium/Widgets/widgets.css' import { TDTLayerType, @@ -22,20 +21,15 @@ import { } from '@/utils/map/TDTProvider' import { initViewer, perfViewer, showNavigator } from '@/utils/map/sceneViewer' import { flyToChina } from '@/utils/map/camera' -import ProfileAnalysis from "@/components/ProfileAnalysis.vue"; import MeasureDistance from "@/assets/js/cesium-map/measureDistance"; const viewerDivRef = ref<HTMLDivElement>() let viewer: Viewer -// window.CESIUM_BASE_URL = 'node_modules/cesium/Build/Cesium/' -window.CESIUM_BASE_URL = 'public/Cesium/' //打包部署 - +window.CESIUM_BASE_URL = 'public/Cesium/' Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3YjU4MjJlMS0wMWE4LTRhOWQtYjQ1OC04MTgzMzFhMzQ5ZjAiLCJpZCI6MTE1ODUxLCJpYXQiOjE2NjkyMDM1MzN9.8ajEuv3VKYg8wvFiQlUWWY6Ng6JfY4PuVgRyStL1B-E' -// 绑定地形剖面图弹窗 -let profileChart = ref<InstanceType<typeof ProfileAnalysis> | null>(null) -let linePoints = ref<number>(-1) + onMounted(() => { //初始化 @@ -63,29 +57,17 @@ onMounted(() => { // 挂载在window,供全局组件共享 window.viewer = viewer - window.measureViewer = new MeasureDistance(viewer); - //绘制多边形 - // const drawPolyline = new CreatePolyline(viewer,false,true,{}) - // linePoints.value = drawPolyline.positions.length - // drawPolyline.start() - // profileChart.value?.openDia() - // - // 绘制地形剖面线 - // profileChart.value?.drawChart_TerrainProfile([0,1,2,3,4,5,6,7,8,9], - // [-70,800,300,400,23,232,435,243,234,343]) -}) - -watch(linePoints, (newValue, OldValue): void => { - console.log("数据变化了",linePoints.value, newValue, OldValue) }) -</script> +</script> +<!--样式修改--> <style scoped> #cesium-viewer { width: 100%; height: 100%; } + </style> diff --git a/src/components/toolbar.vue b/src/components/toolbar.vue index f07673d..f442454 100644 --- a/src/components/toolbar.vue +++ b/src/components/toolbar.vue @@ -13,26 +13,17 @@ import {ref} from "vue"; import {useStaticStore} from "@/store/staticOptions.js"; import {login, requestAirline} from "@/assets/js/request.js"; import {dataProcess, getAirline} from "@/assets/js/websocketProtocol.ts"; +import SpatialAnalysis from "@/components/SpatialAnalysis.vue"; const message = useMessage(); -let file, SceneValue; +let SceneValue; let showModal = ref(false); let hasPlane = ref(false); let store = useStaticStore(); -// -------------------------------------- - - +const spatialAnalyse= ref(null) SceneValue = ref('untrace'); -let sceneOptions= [ - { - label: '第三视角跟随', - value: 'fallow' - },{ - label: '不跟随', - value: 'untrace' - } -]; + function handleSceneSelect(key){ if(!hasPlane.value) return; @@ -42,50 +33,9 @@ function handleSceneSelect(key){ window.viewer.trackedEntity = window.viewer.entities.getById('websocket-flying-plane'); } } -let EditOptions = [ - { - label: '查询航线', - key: 'requestLine' - }, - { - label: '航线管理', - key: 'manage' - }] - let layerValue = ref('layer1'); let barIsOpen = ref(true); -let MeasureOptions = [ - { - label: '距离测量', - key: 'distance' - }, - { - label: '面积测量', - key: 'area' - }, - { - label: '清除', - key: 'clear' - }, -] -let DrawOptions = [ - { - label: '多边形', - key: 'polygon' - }, - { - label: '矩形', - key: 'rec' - },{ - label: '圆形', - key: 'circle' - }, - { - label: '清除', - key: 'clear' - }, -] function handleEditSelect(key) { if(key === 'requestLine') { getUavAirline() @@ -94,6 +44,19 @@ function handleEditSelect(key) { // 航线管理页面 } } +function handleAnalyseSelect(key) { + if(key === 'visibility') { + store.analysisVars.analysisType = 2 + //弹出参数窗口 + spatialAnalyse.value?.openParamsDialog() + } + if(key === 'profile') { + store.analysisVars.analysisType = 1 + //弹出参数窗口 + spatialAnalyse.value?.openParamsDialog() + } + +} function handleDrawSelect(key) { if(key === 'clear') { @@ -173,6 +136,10 @@ function measureArea() { window.measureViewer.activateAreaMeasure(); } +/** + * 连接websocket + * @returns {Promise<void>} + */ async function connectWebSocket() { await login(store.temp.userName, store.temp.password).then(rsp => { let resData = JSON.parse(rsp.data.data) @@ -185,9 +152,9 @@ async function connectWebSocket() { }) if(sessionStorage.getItem('token') === 'err') return - store.server.ws = new WebSocket('ws://123.57.54.1:8048/htfp/websocket/uavGlobal/sysUser003', sessionStorage.getItem('token')) + store.webskt.ws = new WebSocket('ws://123.57.54.1:8048/htfp/websocket/uavGlobal/sysUser003', sessionStorage.getItem('token')) // store.server.ws = new WebSocket('ws://'+store.server.ws_config.address+':'+store.server.ws_config.port); - store.server.ws.onmessage = (event) => { + store.webskt.ws.onmessage = (event) => { //收到消息后的处理流程.... let data = dataProcess(JSON.parse(event.data)) console.log(data); @@ -203,12 +170,18 @@ async function connectWebSocket() { }; } +/** + * 关闭websocket连接 + */ function closeWS(){ - if(store.server.ws){ - store.server.ws.close(); + if(store.webskt.ws){ + store.webskt.ws.close(); } } +/** + * 请求航线接口 + */ function getUavAirline() { if(sessionStorage.getItem('uavId')){ requestAirline(sessionStorage.getItem('uavId')).then(rsp => { @@ -219,11 +192,13 @@ function getUavAirline() { message.warning('当前未连接飞机') } } + + </script> <template> <n-flex id="panel"> - <n-popselect v-model:value="layerValue" :options="store.templateValue.layerOptions" size="medium"> + <n-popselect v-model:value="layerValue" :options="store.menuOptions.layerOptions" size="medium"> <n-button tertiary circle type="warning"> <template #icon> <n-icon><Layers/></n-icon> @@ -240,31 +215,30 @@ function getUavAirline() { </template> <span> 添加数据 </span> </n-tooltip> - <n-tooltip placement="bottom" trigger="hover"> - <template #trigger> - <n-button tertiary circle type="warning"> - <template #icon> - <n-icon><TerrainSharp/></n-icon> - </template> - </n-button> - </template> - <span> 地形分析 </span> - </n-tooltip> - <n-dropdown :options="EditOptions" @select="handleEditSelect"> + + <n-dropdown :options="store.menuOptions.AnalyzeOptions" @select="handleAnalyseSelect"> + <n-button tertiary circle type="warning"> + <template #icon> + <n-icon><TerrainSharp/></n-icon> + </template> + </n-button> + </n-dropdown> + + <n-dropdown :options="store.menuOptions.EditOptions" @select="handleEditSelect"> <n-button tertiary circle type="warning"> <template #icon> <n-icon><CreateOutline/></n-icon> </template> </n-button> </n-dropdown> - <n-dropdown :options="MeasureOptions" @select="handleSelect"> + <n-dropdown :options="store.menuOptions.MeasureOptions" @select="handleSelect"> <n-button tertiary circle type="warning"> <template #icon> <n-icon><RulerAlt/></n-icon> </template> </n-button> </n-dropdown> - <n-dropdown :options="DrawOptions" @select="handleDrawSelect"> + <n-dropdown :options="store.menuOptions.DrawOptions" @select="handleDrawSelect"> <n-button tertiary circle type="warning"> <template #icon> <n-icon><DrawPolygon/></n-icon> @@ -281,8 +255,8 @@ function getUavAirline() { </template> <span> WebSocket配置 </span> </n-tooltip> - <n-popselect v-model:value="SceneValue" :options="sceneOptions" @update:value="handleSceneSelect" - size="medium"> + <n-popselect v-model:value="SceneValue" :options="store.menuOptions.sceneOptions" + @update:value="handleSceneSelect" size="medium"> <!-- :disabled="!hasPlane" --> <n-button tertiary circle type="warning"> @@ -309,15 +283,15 @@ function getUavAirline() { </template> <div style="margin: 2rem 2rem .5rem 1rem"> <n-space> - <n-form ref="formRef" :model="store.server.ws_config" + <n-form ref="formRef" :model="store.webskt.ws_config" label-placement="left" label-width="auto" require-mark-placement="right-hanging"> <n-form-item label="服务器地址"> - <n-input v-model:value="store.server.ws_config.address" placeholder="127.0.0.1"/> + <n-input v-model:value="store.webskt.ws_config.address" placeholder="127.0.0.1"/> </n-form-item> <n-form-item label="端口号"> - <n-input-number v-model:value="store.server.ws_config.port" placeholder=8000 /> + <n-input-number v-model:value="store.webskt.ws_config.port" placeholder=8000 /> </n-form-item> </n-form> </n-space> @@ -328,6 +302,8 @@ function getUavAirline() { </n-space> </div> </n-modal> + + <SpatialAnalysis ref="spatialAnalyse"></SpatialAnalysis> </template> <style scoped> diff --git a/src/router/index.js b/src/router/index.js index a121f94..27eed2b 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -7,6 +7,11 @@ const router = createRouter({ path: '/', name: 'Home', component: ()=>import( '@/components/HomePage.vue'), + }, + { + path: '/test', + name: 'Test', + component: ()=>import('@/components/UnitTest.vue') } ] }); diff --git a/src/store/staticOptions.js b/src/store/staticOptions.js index 2a20428..7d5ba30 100644 --- a/src/store/staticOptions.js +++ b/src/store/staticOptions.js @@ -1,5 +1,6 @@ import {defineStore} from "pinia"; import cesiumAirPlane from "@/assets/models/Cesium_Air.glb"; +import {ref} from "vue"; export const useStaticStore = defineStore('staticOptions',{ state: ()=>{ return { @@ -8,10 +9,42 @@ export const useStaticStore = defineStore('staticOptions',{ password:"sysUser003", token:"", }, - server: { + menuOptions:{ + EditOptions: [ + {label: '查询航线', key: 'requestLine'}, + {label: '航线管理', key: 'manage'} + ], + sceneOptions:[ + {label: '第三视角跟随', value: 'fallow'}, + {label: '不跟随', value: 'untrace'} + ], + MeasureOptions:[ + {label: '距离测量', key: 'distance'}, + {label: '面积测量', key: 'area'}, + {label: '清除', key: 'clear'}, + ], + DrawOptions:[ + {label: '多边形', key: 'polygon'}, + {label: '矩形', key: 'rec'}, + {label: '圆形', key: 'circle'}, + {label: '清除', key: 'clear'}, + ], + AnalyzeOptions:[ + {label: '通视分析', key: 'visibility'}, + {label: '地形剖面', key: 'profile'} + ], + //图层选项,动态 + layerOptions: [ + { + label: '图层1', + value: 'layer1' + } + ], + }, + webskt: { ws: null, isOpen: false, - ws_config:{ + ws_config: { address: '127.0.0.1', port: 8000, }, @@ -20,15 +53,10 @@ export const useStaticStore = defineStore('staticOptions',{ defaultAirPlane: cesiumAirPlane, }, hasPlane: false, - templateValue:{ - //图层选项,动态 - layerOptions: [ - { - label: '图层1', - value: 'layer1' - } - ], - + analysisVars: { + //当前的空间分析类型,剖面1 通视2 + analysisType: -1, + profilePts: [], //地形分析的点数组 } } } diff --git a/src/utils/map/SpatialAnalysis.ts b/src/utils/map/SpatialAnalysis.ts index 2d7d765..400a53f 100644 --- a/src/utils/map/SpatialAnalysis.ts +++ b/src/utils/map/SpatialAnalysis.ts @@ -6,6 +6,7 @@ import {getDistance, getElevation} from "@/utils/map/geocomputation.ts"; import {Cartesian3, Viewer} from "cesium"; +import * as echarts from "echarts"; type ProfileResult = { distanceArray:number[], elevationArray:number[], @@ -24,23 +25,29 @@ export function elevationProfile(viewer: Viewer, start:Cartesian3, end:Cartesian let distanceFromStart:number[] = [] //断点至起点的距离m // 计算首尾点距离 m let totalLen = getDistance(start, end) * 1000 - // 获取起点高度 - breakPointsHeight.push(getElevation(viewer, start)) + // 获取起点高度及其与起点的距离 + breakPointsHeight.push(Math.round(getElevation(viewer, start))) distanceFromStart.push(0) - //断点数量 - let breakNum = Math.floor(totalLen/interval) - // 如果采样间隔小于首尾点距离,则获取每个断点的坐标 并获取其高度 - if(breakNum>=1){ - for (let i = 1; i <= breakNum; i++) { - let breakP = Cartesian3.lerp(start, end, i/breakNum, new Cartesian3()) - breakPointsHeight.push(getElevation(viewer, breakP)) - distanceFromStart.push(getDistance(start,breakP)*1000) + + //获取中间断点的高度及其与起点的距离 + if(interval > 0){ + //断点数量 + let breakNum = Math.floor(totalLen/interval) + // 如果采样间隔小于首尾点距离,则获取每个断点的坐标 并获取其高度 + if(breakNum>=1){ + for (let i = 1; i < breakNum; i++) { + let breakP = Cartesian3.lerp(start, end, i/breakNum, new Cartesian3()) + breakPointsHeight.push(Math.round(getElevation(viewer, breakP))) //单位 米 + distanceFromStart.push(Math.round(getDistance(start,breakP)*1000)) //单位 米 + } } } - // 获取终点高度 - breakPointsHeight.push(getElevation(viewer, end)) - distanceFromStart.push(totalLen) - return { distanceArray:distanceFromStart, elevationArray:breakPointsHeight } + + // 获取终点高度及其与起点的距离 + breakPointsHeight.push(Math.round(getElevation(viewer, end))) + distanceFromStart.push(Math.round(totalLen)) + + return { distanceArray: distanceFromStart, elevationArray: breakPointsHeight } } /** @@ -50,12 +57,18 @@ export function elevationProfile(viewer: Viewer, start:Cartesian3, end:Cartesian * @param interval 线段采样间隔 m为单位 * @return 从折线起点至终点剖面线的海拔高度数组 */ -export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[],interval: number){ - let result:ProfileResult = { distanceArray:[], elevationArray:[] } - for (let i = 0; i < polyline.length - 2; i++) { - let temp = elevationProfile(viewer,polyline[i],polyline[i+1],interval) +export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[], interval: number){ + let result: ProfileResult = { distanceArray:[], elevationArray:[] } + let temp_dis = 0 //每两点之间的距离 + for (let i = 0; i <= polyline.length - 2; i++) { + let temp = elevationProfile(viewer, polyline[i], polyline[i+1], interval) result.elevationArray = result.elevationArray.concat(temp.elevationArray) - result.distanceArray = result.distanceArray.concat(temp.distanceArray) + temp.distanceArray.forEach(distance => { + result.distanceArray.push(distance+temp_dis) + }) + if(temp.distanceArray.length > 0){ + temp_dis += temp.distanceArray[temp.distanceArray.length-1] + } } return result } @@ -66,19 +79,21 @@ export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[],interval: n * @param viewer 地图 * @param viewpoint 视点 * @param target 目标点 - * @param h 视点地面抬高,默认为1m + * @param h1 视点地面挂高 m + * @param h2 目标点地面挂高 m + * @param curvature 是否考虑地球曲率 * @param breakNum 采样间隔,默认为100个断点 */ export function visibilityAnalyse(viewer: Viewer, viewpoint:Cartesian3, target:Cartesian3, - h= 1, breakNum = 100) { + h1:number, h2:number,curvature:boolean, breakNum = 100) { // 获取视点高度和目标点高度 - let viewpointH = getElevation(viewer, viewpoint) + h - let targetH = getElevation(viewer, target) - if (viewpointH === -9999.2024 + h ) { + let viewpointH = getElevation(viewer, viewpoint) + h1 + let targetH = getElevation(viewer, target) + h2 + if (viewpointH === -9999.2024 + h1 ) { console.log("无法获取视点海拔高度!") return undefined } - if (targetH === -9999.2024 ) { + if (targetH === -9999.2024 + h2 ) { console.log("无法获取目标点海拔高度!") return undefined } @@ -98,5 +113,350 @@ export function visibilityAnalyse(viewer: Viewer, viewpoint:Cartesian3, target:C return false } } + + if(curvature){ + let Rmax = 2.898 * (Math.sqrt(h1)+Math.sqrt(h2)) + if (Rmax < totalLen/1000.0) return false + } return true } + +/** + * 绘制地形剖面折线图 + * @param xData x数组 + * @param yData y数组,以面积线绘制 + */ +export function drawEchartsProfileAnalyse(xData:number[], yData:number[]) { + let myChart = echarts.init(document.getElementById('profileEChart')) + // 绘制图表 + myChart.setOption({ + legend: { + show: true, + type: 'plain', + top: '5%', + data:[ + { + name: 'groundLine', + itemStyle: 'inherit', + lineStyle: 'inherit', + }, + ] + }, + tooltip: { + show: true, + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + formatter:'地表高度: {c0}' + }, + xAxis: { + data: xData, + name: '距离/米', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + axisLine:{ + onZero: false, + show: true, // 是否显示坐标轴轴线 + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + }, + axisLabel: { + formatter: '{value}', + margin: 5, + }, + axisTick: { + show: true, // 是否显示坐标轴刻度 + inside: true, // 坐标轴刻度是否朝内,默认朝外 + alignWithLabel: true, + lineStyle: { + color: '#000000', //刻度线的颜色 + type: 'solid', //坐标轴线线的类型(solid实线类型;dashed虚线类型;dotted点状类型) + }, + } + }, + yAxis: { + type: 'value', + name: '高度/米', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + position: 'left', + axisLabel: { + formatter: '{value}' + }, + axisLine: { + show: true, + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + } + }, + series: [ + { + name:'groundLine', + type: 'line', + data: yData, + areaStyle: { + color: '#37a5fb', + opacity: 0.5 + } + } + ] + }); +} + + +/** + * 绘制折线图(航线碰撞检测) + * @param xData x数组 + * @param yData y数组,以面积线绘制 + * @param yData2 y数组,以折线绘制 + */ +export const drawEchartsAirlineDetect = (xData:number[],yData:number[],yData2:number[]) => { + let myChart = echarts.init(document.getElementById('profileEChart')) + // 绘制图表 + myChart.setOption({ + legend:{ + show: true, + type: 'plain', + top: '7%', + data:[ + { + name: 'groundLine', + itemStyle: 'inherit', + lineStyle: 'inherit', + }, + { + name: 'airLine', + itemStyle: 'inherit', + lineStyle: 'inherit', + }] + }, + tooltip: { + show: true, + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + formatter:'地表高度: {c0}<br>航线高度: {c1}' + }, + xAxis: { + data: xData, + name: '距离', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + axisLine:{ + onZero: false, + show: true, // 是否显示坐标轴轴线 + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + }, + axisLabel: { + formatter: '{value} m', + margin: 5, + }, + axisTick: { + show: true, // 是否显示坐标轴刻度 + inside: true, // 坐标轴刻度是否朝内,默认朝外 + alignWithLabel: true, + lineStyle: { + color: '#000000', //刻度线的颜色 + type: 'solid', //坐标轴线线的类型(solid实线类型;dashed虚线类型;dotted点状类型) + }, + } + }, + yAxis: { + type: 'value', + name: '高度', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + position: 'left', + axisLabel: { + formatter: '{value} m' + }, + axisLine: { + show: true, + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + } + }, + series: [ + { + name:'groundLine', + type: 'line', + data: yData, + areaStyle: { + color: '#37a5fb', + opacity: 0.5 + } + }, + { + name:'airLine', + type: 'line', + data: yData2, + } + ] + }); +} + + +/** + * 绘制通视分析图 面积线和直线 + * @param xData x数组,地面距离 + * @param yData y数组,以面积线绘制 + * @param startHeight 视线起始点高度(包含地面挂高) + * @param endHeight 视线终点高度(包含地面挂高) + */ +export const drawEchartsVisibility = (xData:number[], yData:number[],startHeight:number, endHeight:number) => { + console.group() + console.log(0, startHeight) + console.log(xData.at(-1), endHeight) + console.groupEnd() + console.log(xData) + let myChart = echarts.init(document.getElementById('profileEChart')) //Echarts-UnitTest + // 绘制图表 + myChart.setOption({ + legend: { + show: true, + type: 'plain', + top: '7%', + data: [ + { //地形剖面图例 + name: '剖面线', + itemStyle: { + fontSize: 18 + }, + lineStyle: 'inherit', + }, + { //视线图例 + name: '视线', + lineStyle: { + type: 'dotted', + width: 3, + color: '#f8364d' + }, + itemStyle: { + fontSize: 18 + } + } + ], + selectedMode: false, //图例选择模式关闭 + }, + tooltip: { + show: true, + trigger: 'axis', + axisPointer: { + type: 'cross' + }, + formatter:'地表高度: {c0}' + }, + xAxis: { + boundaryGap : false, + max: (value:any)=>{ + return value.max * 1.01; + }, + min: 0, + data: xData, + name: '距离/m', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + axisLine:{ + onZero: false, + show: true, // 是否显示坐标轴轴线 + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + }, + axisLabel: { + formatter: '{value}', + margin: 5, + }, + axisTick: { + show: true, // 是否显示坐标轴刻度 + inside: true, // 坐标轴刻度是否朝内,默认朝外 + alignWithLabel: true, + lineStyle: { + color: '#000000', //刻度线的颜色 + type: 'solid', //坐标轴线线的类型(solid实线类型;dashed虚线类型;dotted点状类型) + }, + } + }, + yAxis: { + max: (value:any)=>{ + return Math.floor(Math.max(value.max,startHeight,endHeight)*1.01) + }, + min: (value:any)=>{ + return Math.floor(Math.min(value.min,startHeight,endHeight)*0.99) + }, + type: 'value', + name: '高度/ m', + nameTextStyle: { + fontWeight:'bolder', + fontSize: 14 + }, + nameLocation: 'end', + position: 'left', + axisLabel: { + formatter: '{value}' + }, + axisLine: { + show: true, + symbol: ['none', 'arrow'], + symbolSize: [7, 10] + } + }, + series: [ + { + name:'剖面线', + type: 'line', + data: yData, + areaStyle: { + color: '#37a5fb', + opacity: 0.5 + }, + markLine: { + data: [ + [ + {coord: ['0', startHeight.toString()]}, + {coord: [xData.at(-1)?.toString(), endHeight.toString()]} + // Markline中的坐标点必须为string,否则异常 + ] + ], + symbol:['circle', 'arrow'], + label:{ + formatter: "模 拟 视 线", + show: true, + position: 'middle', + fontSize: 15, + fontWeight: 'bold', + color: '#e73d3d', + }, + lineStyle: { //标注线样式 + type: 'dashed', + color: 'red', + with: 10 + } + }, + }, + { /* 视线的series */ + type: 'line', + symbol: 'none', + name: '视线', + color: 'transparent' + } + ], + }); +} diff --git a/update-index.mjs b/update-index.mjs deleted file mode 100644 index b8150a8..0000000 --- a/update-index.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import fs from 'fs'; - -console.time('转换用时'); -const distPath = './dist/index.html'; //打包路径的index.html -let htmlText = fs.readFileSync(distPath, 'utf8'); -let resultText = ''; -let htmlArr = htmlText.match(/.*\n/g) || []; -htmlArr.forEach(str => { - str = str.replace(/\s?nomodule\s?/g,' '); - str = str.replace(/\s?crossorigin\s?/g,' '); - str = str.replace(/data-src/g,'src'); - if(!/type="module"/i.test(str)) - resultText += str; -}); -fs.writeFileSync(distPath,resultText,'utf8'); -console.timeEnd('转换用时'); \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index b737f7c..df4b634 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -19,7 +19,7 @@ export default defineConfig({ }), ], base: './', - build:{ + build: { target: ['es2015', 'chrome63'], }, resolve: {