feature: 地形分析、通视分析交互组件和逻辑实现

devzpx
zhaipx 2 weeks ago
parent 12872e61ca
commit bb042154cd

@ -24,11 +24,6 @@
cesium="true" cesium="true"
src="public/Cesium/Cesium.js" src="public/Cesium/Cesium.js"
></script> ></script>
<!-- <script
type="text/javascript"
cesium="true"
src="https://api.tianditu.gov.cn/cdn/demo/sanwei/static/cesium/Cesium.js"
></script> -->
<script <script
type="text/javascript" type="text/javascript"
cesium="true" cesium="true"

@ -6,7 +6,6 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"build-localhost": "vite build && node update-index.mjs",
"preview": "vite preview", "preview": "vite preview",
"lint": "eslint src", "lint": "eslint src",
"fix": "eslint src --fix", "fix": "eslint src --fix",

@ -1,6 +1,6 @@
import * as Cesium from 'cesium' import * as Cesium from 'cesium'
import {Cartesian3} from 'cesium' import {Cartesian3} from 'cesium'
import {geojson2kml} from "@/assets/js/DataUtils.js"; import {useStaticStore} from "@/store/staticOptions.js";
export default class MeasureDistance { export default class MeasureDistance {
constructor(viewer) { constructor(viewer) {
@ -8,17 +8,18 @@ export default class MeasureDistance {
this.scene = viewer.scene this.scene = viewer.scene
this.isMeasure = false this.isMeasure = false
this.positions = [] this.positions = []
this.temPositions = [] // 鼠标移动时产生的临时点 this.temPositions = [] // 距离测量-鼠标移动时产生的临时点
this.vertexEntities = [] // 节点元素 this.vertexEntities = [] // 距离测量-节点元素
this.lineEntity = undefined // 距离测量折线元素 this.lineEntity = undefined // 距离测量-折线元素
this.lineEntitys = [] // 距离测量折线元素数组,每完成一次距离测量,将折线元素加入此数组 this.lineEntitys = [] // 距离测量-折线元素数组,每完成一次距离测量,将折线元素加入此数组
this.totalDistance = 0 // 总距离 this.totalDistance = 0 // 距离测量-总距离
this.activeShapePoints = [] //面积测量多边形顶点 this.activeShapePoints = [] //面积测量-多边形顶点
this.polygon = undefined this.polygon = undefined
this.measAreaStatus = false //是否在测量面积 this.measAreaStatus = false //是否在测量面积
this.graphics = [] this.analyseGraphs = [] //分析工具的图形容器
this.graphics = [] //图形绘制容器
this.dynamicData = { this.dynamicData = {
lon: undefined, lon: undefined,
lat: undefined, lat: undefined,
@ -30,15 +31,90 @@ export default class MeasureDistance {
borderWidth: 1, borderWidth: 1,
material: Cesium.Color.GREEN.withAlpha(0.5), 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){ updateDynamicData(data){
this.dynamicData.lon = data.lon; this.dynamicData.lon = data.lon;
this.dynamicData.lat = data.lat; this.dynamicData.lat = data.lat;
this.dynamicData.alt = data.alt; this.dynamicData.alt = data.alt;
this.dynamicData.heading = data.heading; this.dynamicData.heading = data.heading;
this.viewer.scene.requestRender() this.viewer.scene.requestRender()
} }
addKml(kml_file){ addKml(kml_file){
@ -692,7 +768,7 @@ export default class MeasureDistance {
}) })
this.graphics = [] this.graphics = []
} }
/********************** 图形绘制 ***************************************/ /********************** 实时图形绘制 ***************************************/
drawGraphics(key){ drawGraphics(key){
this.viewer.scene.globe.depthTestAgainstTerrain = true this.viewer.scene.globe.depthTestAgainstTerrain = true
// 清除可能会用到的监听事件 // 清除可能会用到的监听事件
@ -920,4 +996,31 @@ export default class MeasureDistance {
} }
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK) }, 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,
},
})
}
} }

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

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

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

@ -7,12 +7,11 @@
--> -->
<template> <template>
<div id="cesium-viewer" ref="viewerDivRef"></div> <div id="cesium-viewer" ref="viewerDivRef"></div>
<ProfileAnalysis ref="profileChart"></ProfileAnalysis>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {onMounted, ref, watch} from 'vue' import {onMounted, ref} from 'vue'
import {Viewer, Ion, CustomDataSource, Cartesian3} from 'cesium' import {Viewer, Ion, CustomDataSource} from 'cesium'
import 'cesium/Build/Cesium/Widgets/widgets.css' import 'cesium/Build/Cesium/Widgets/widgets.css'
import { import {
TDTLayerType, TDTLayerType,
@ -22,20 +21,15 @@ import {
} from '@/utils/map/TDTProvider' } from '@/utils/map/TDTProvider'
import { initViewer, perfViewer, showNavigator } from '@/utils/map/sceneViewer' import { initViewer, perfViewer, showNavigator } from '@/utils/map/sceneViewer'
import { flyToChina } from '@/utils/map/camera' import { flyToChina } from '@/utils/map/camera'
import ProfileAnalysis from "@/components/ProfileAnalysis.vue";
import MeasureDistance from "@/assets/js/cesium-map/measureDistance"; import MeasureDistance from "@/assets/js/cesium-map/measureDistance";
const viewerDivRef = ref<HTMLDivElement>() const viewerDivRef = ref<HTMLDivElement>()
let viewer: Viewer 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 = Ion.defaultAccessToken =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3YjU4MjJlMS0wMWE4LTRhOWQtYjQ1OC04MTgzMzFhMzQ5ZjAiLCJpZCI6MTE1ODUxLCJpYXQiOjE2NjkyMDM1MzN9.8ajEuv3VKYg8wvFiQlUWWY6Ng6JfY4PuVgRyStL1B-E' 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI3YjU4MjJlMS0wMWE4LTRhOWQtYjQ1OC04MTgzMzFhMzQ5ZjAiLCJpZCI6MTE1ODUxLCJpYXQiOjE2NjkyMDM1MzN9.8ajEuv3VKYg8wvFiQlUWWY6Ng6JfY4PuVgRyStL1B-E'
//
let profileChart = ref<InstanceType<typeof ProfileAnalysis> | null>(null)
let linePoints = ref<number>(-1)
onMounted(() => { onMounted(() => {
// //
@ -63,29 +57,17 @@ onMounted(() => {
// window // window
window.viewer = viewer window.viewer = viewer
window.measureViewer = new MeasureDistance(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> <style scoped>
#cesium-viewer { #cesium-viewer {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
</style> </style>

@ -13,26 +13,17 @@ import {ref} from "vue";
import {useStaticStore} from "@/store/staticOptions.js"; import {useStaticStore} from "@/store/staticOptions.js";
import {login, requestAirline} from "@/assets/js/request.js"; import {login, requestAirline} from "@/assets/js/request.js";
import {dataProcess, getAirline} from "@/assets/js/websocketProtocol.ts"; import {dataProcess, getAirline} from "@/assets/js/websocketProtocol.ts";
import SpatialAnalysis from "@/components/SpatialAnalysis.vue";
const message = useMessage(); const message = useMessage();
let file, SceneValue; let SceneValue;
let showModal = ref(false); let showModal = ref(false);
let hasPlane = ref(false); let hasPlane = ref(false);
let store = useStaticStore(); let store = useStaticStore();
// -------------------------------------- const spatialAnalyse= ref(null)
SceneValue = ref('untrace'); SceneValue = ref('untrace');
let sceneOptions= [
{
label: '第三视角跟随',
value: 'fallow'
},{
label: '不跟随',
value: 'untrace'
}
];
function handleSceneSelect(key){ function handleSceneSelect(key){
if(!hasPlane.value) return; if(!hasPlane.value) return;
@ -42,50 +33,9 @@ function handleSceneSelect(key){
window.viewer.trackedEntity = window.viewer.entities.getById('websocket-flying-plane'); window.viewer.trackedEntity = window.viewer.entities.getById('websocket-flying-plane');
} }
} }
let EditOptions = [
{
label: '查询航线',
key: 'requestLine'
},
{
label: '航线管理',
key: 'manage'
}]
let layerValue = ref('layer1'); let layerValue = ref('layer1');
let barIsOpen = ref(true); 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) { function handleEditSelect(key) {
if(key === 'requestLine') { if(key === 'requestLine') {
getUavAirline() 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) { function handleDrawSelect(key) {
if(key === 'clear') { if(key === 'clear') {
@ -173,6 +136,10 @@ function measureArea() {
window.measureViewer.activateAreaMeasure(); window.measureViewer.activateAreaMeasure();
} }
/**
* 连接websocket
* @returns {Promise<void>}
*/
async function connectWebSocket() { async function connectWebSocket() {
await login(store.temp.userName, store.temp.password).then(rsp => { await login(store.temp.userName, store.temp.password).then(rsp => {
let resData = JSON.parse(rsp.data.data) let resData = JSON.parse(rsp.data.data)
@ -185,9 +152,9 @@ async function connectWebSocket() {
}) })
if(sessionStorage.getItem('token') === 'err') return 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 = 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)) let data = dataProcess(JSON.parse(event.data))
console.log(data); console.log(data);
@ -203,12 +170,18 @@ async function connectWebSocket() {
}; };
} }
/**
* 关闭websocket连接
*/
function closeWS(){ function closeWS(){
if(store.server.ws){ if(store.webskt.ws){
store.server.ws.close(); store.webskt.ws.close();
} }
} }
/**
* 请求航线接口
*/
function getUavAirline() { function getUavAirline() {
if(sessionStorage.getItem('uavId')){ if(sessionStorage.getItem('uavId')){
requestAirline(sessionStorage.getItem('uavId')).then(rsp => { requestAirline(sessionStorage.getItem('uavId')).then(rsp => {
@ -219,11 +192,13 @@ function getUavAirline() {
message.warning('当前未连接飞机') message.warning('当前未连接飞机')
} }
} }
</script> </script>
<template> <template>
<n-flex id="panel"> <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"> <n-button tertiary circle type="warning">
<template #icon> <template #icon>
<n-icon><Layers/></n-icon> <n-icon><Layers/></n-icon>
@ -240,31 +215,30 @@ function getUavAirline() {
</template> </template>
<span> 添加数据 </span> <span> 添加数据 </span>
</n-tooltip> </n-tooltip>
<n-tooltip placement="bottom" trigger="hover">
<template #trigger> <n-dropdown :options="store.menuOptions.AnalyzeOptions" @select="handleAnalyseSelect">
<n-button tertiary circle type="warning"> <n-button tertiary circle type="warning">
<template #icon> <template #icon>
<n-icon><TerrainSharp/></n-icon> <n-icon><TerrainSharp/></n-icon>
</template> </template>
</n-button> </n-button>
</template> </n-dropdown>
<span> 地形分析 </span>
</n-tooltip> <n-dropdown :options="store.menuOptions.EditOptions" @select="handleEditSelect">
<n-dropdown :options="EditOptions" @select="handleEditSelect">
<n-button tertiary circle type="warning"> <n-button tertiary circle type="warning">
<template #icon> <template #icon>
<n-icon><CreateOutline/></n-icon> <n-icon><CreateOutline/></n-icon>
</template> </template>
</n-button> </n-button>
</n-dropdown> </n-dropdown>
<n-dropdown :options="MeasureOptions" @select="handleSelect"> <n-dropdown :options="store.menuOptions.MeasureOptions" @select="handleSelect">
<n-button tertiary circle type="warning"> <n-button tertiary circle type="warning">
<template #icon> <template #icon>
<n-icon><RulerAlt/></n-icon> <n-icon><RulerAlt/></n-icon>
</template> </template>
</n-button> </n-button>
</n-dropdown> </n-dropdown>
<n-dropdown :options="DrawOptions" @select="handleDrawSelect"> <n-dropdown :options="store.menuOptions.DrawOptions" @select="handleDrawSelect">
<n-button tertiary circle type="warning"> <n-button tertiary circle type="warning">
<template #icon> <template #icon>
<n-icon><DrawPolygon/></n-icon> <n-icon><DrawPolygon/></n-icon>
@ -281,8 +255,8 @@ function getUavAirline() {
</template> </template>
<span> WebSocket配置 </span> <span> WebSocket配置 </span>
</n-tooltip> </n-tooltip>
<n-popselect v-model:value="SceneValue" :options="sceneOptions" @update:value="handleSceneSelect" <n-popselect v-model:value="SceneValue" :options="store.menuOptions.sceneOptions"
size="medium"> @update:value="handleSceneSelect" size="medium">
<!-- :disabled="!hasPlane" --> <!-- :disabled="!hasPlane" -->
<n-button tertiary circle type="warning"> <n-button tertiary circle type="warning">
@ -309,15 +283,15 @@ function getUavAirline() {
</template> </template>
<div style="margin: 2rem 2rem .5rem 1rem"> <div style="margin: 2rem 2rem .5rem 1rem">
<n-space> <n-space>
<n-form ref="formRef" :model="store.server.ws_config" <n-form ref="formRef" :model="store.webskt.ws_config"
label-placement="left" label-placement="left"
label-width="auto" label-width="auto"
require-mark-placement="right-hanging"> require-mark-placement="right-hanging">
<n-form-item label="服务器地址"> <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>
<n-form-item label="端口号"> <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-item>
</n-form> </n-form>
</n-space> </n-space>
@ -328,6 +302,8 @@ function getUavAirline() {
</n-space> </n-space>
</div> </div>
</n-modal> </n-modal>
<SpatialAnalysis ref="spatialAnalyse"></SpatialAnalysis>
</template> </template>
<style scoped> <style scoped>

@ -7,6 +7,11 @@ const router = createRouter({
path: '/', path: '/',
name: 'Home', name: 'Home',
component: ()=>import( '@/components/HomePage.vue'), component: ()=>import( '@/components/HomePage.vue'),
},
{
path: '/test',
name: 'Test',
component: ()=>import('@/components/UnitTest.vue')
} }
] ]
}); });

@ -1,5 +1,6 @@
import {defineStore} from "pinia"; import {defineStore} from "pinia";
import cesiumAirPlane from "@/assets/models/Cesium_Air.glb"; import cesiumAirPlane from "@/assets/models/Cesium_Air.glb";
import {ref} from "vue";
export const useStaticStore = defineStore('staticOptions',{ export const useStaticStore = defineStore('staticOptions',{
state: ()=>{ state: ()=>{
return { return {
@ -8,10 +9,42 @@ export const useStaticStore = defineStore('staticOptions',{
password:"sysUser003", password:"sysUser003",
token:"", 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, ws: null,
isOpen: false, isOpen: false,
ws_config:{ ws_config: {
address: '127.0.0.1', address: '127.0.0.1',
port: 8000, port: 8000,
}, },
@ -20,15 +53,10 @@ export const useStaticStore = defineStore('staticOptions',{
defaultAirPlane: cesiumAirPlane, defaultAirPlane: cesiumAirPlane,
}, },
hasPlane: false, hasPlane: false,
templateValue:{ analysisVars: {
//图层选项,动态 //当前的空间分析类型剖面1 通视2
layerOptions: [ analysisType: -1,
{ profilePts: [], //地形分析的点数组
label: '图层1',
value: 'layer1'
}
],
} }
} }
} }

@ -6,6 +6,7 @@
import {getDistance, getElevation} from "@/utils/map/geocomputation.ts"; import {getDistance, getElevation} from "@/utils/map/geocomputation.ts";
import {Cartesian3, Viewer} from "cesium"; import {Cartesian3, Viewer} from "cesium";
import * as echarts from "echarts";
type ProfileResult = { type ProfileResult = {
distanceArray:number[], distanceArray:number[],
elevationArray:number[], elevationArray:number[],
@ -24,23 +25,29 @@ export function elevationProfile(viewer: Viewer, start:Cartesian3, end:Cartesian
let distanceFromStart:number[] = [] //断点至起点的距离m let distanceFromStart:number[] = [] //断点至起点的距离m
// 计算首尾点距离 m // 计算首尾点距离 m
let totalLen = getDistance(start, end) * 1000 let totalLen = getDistance(start, end) * 1000
// 获取起点高度 // 获取起点高度及其与起点的距离
breakPointsHeight.push(getElevation(viewer, start)) breakPointsHeight.push(Math.round(getElevation(viewer, start)))
distanceFromStart.push(0) distanceFromStart.push(0)
//获取中间断点的高度及其与起点的距离
if(interval > 0){
//断点数量 //断点数量
let breakNum = Math.floor(totalLen/interval) let breakNum = Math.floor(totalLen/interval)
// 如果采样间隔小于首尾点距离,则获取每个断点的坐标 并获取其高度 // 如果采样间隔小于首尾点距离,则获取每个断点的坐标 并获取其高度
if(breakNum>=1){ if(breakNum>=1){
for (let i = 1; i <= breakNum; i++) { for (let i = 1; i < breakNum; i++) {
let breakP = Cartesian3.lerp(start, end, i/breakNum, new Cartesian3()) let breakP = Cartesian3.lerp(start, end, i/breakNum, new Cartesian3())
breakPointsHeight.push(getElevation(viewer, breakP)) breakPointsHeight.push(Math.round(getElevation(viewer, breakP))) //单位 米
distanceFromStart.push(getDistance(start,breakP)*1000) distanceFromStart.push(Math.round(getDistance(start,breakP)*1000)) //单位 米
}
} }
} }
// 获取终点高度
breakPointsHeight.push(getElevation(viewer, end)) // 获取终点高度及其与起点的距离
distanceFromStart.push(totalLen) breakPointsHeight.push(Math.round(getElevation(viewer, end)))
return { distanceArray:distanceFromStart, elevationArray:breakPointsHeight } 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 * @param interval 线 m
* @return 线线 * @return 线线
*/ */
export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[],interval: number){ export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[], interval: number){
let result:ProfileResult = { distanceArray:[], elevationArray:[] } let result: ProfileResult = { distanceArray:[], elevationArray:[] }
for (let i = 0; i < polyline.length - 2; i++) { let temp_dis = 0 //每两点之间的距离
let temp = elevationProfile(viewer,polyline[i],polyline[i+1],interval) 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.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 return result
} }
@ -66,19 +79,21 @@ export function profileAnalyse(viewer: Viewer, polyline:Cartesian3[],interval: n
* @param viewer * @param viewer
* @param viewpoint * @param viewpoint
* @param target * @param target
* @param h 1m * @param h1 m
* @param h2 m
* @param curvature
* @param breakNum 100 * @param breakNum 100
*/ */
export function visibilityAnalyse(viewer: Viewer, viewpoint:Cartesian3, target:Cartesian3, 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 viewpointH = getElevation(viewer, viewpoint) + h1
let targetH = getElevation(viewer, target) let targetH = getElevation(viewer, target) + h2
if (viewpointH === -9999.2024 + h ) { if (viewpointH === -9999.2024 + h1 ) {
console.log("无法获取视点海拔高度!") console.log("无法获取视点海拔高度!")
return undefined return undefined
} }
if (targetH === -9999.2024 ) { if (targetH === -9999.2024 + h2 ) {
console.log("无法获取目标点海拔高度!") console.log("无法获取目标点海拔高度!")
return undefined return undefined
} }
@ -98,5 +113,350 @@ export function visibilityAnalyse(viewer: Viewer, viewpoint:Cartesian3, target:C
return false return false
} }
} }
if(curvature){
let Rmax = 2.898 * (Math.sqrt(h1)+Math.sqrt(h2))
if (Rmax < totalLen/1000.0) return false
}
return true 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'
}
],
});
}

@ -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('转换用时');

@ -19,7 +19,7 @@ export default defineConfig({
}), }),
], ],
base: './', base: './',
build:{ build: {
target: ['es2015', 'chrome63'], target: ['es2015', 'chrome63'],
}, },
resolve: { resolve: {

Loading…
Cancel
Save