主要版本
This commit is contained in:
60
src/render/App.vue
Normal file
60
src/render/App.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div class="w-h-full fixed">
|
||||
<EarthView ref="earthViewInstance" />
|
||||
</div>
|
||||
<div class="w-h-full fixed grid grid-cols-2 p-10 front-container">
|
||||
<div class="text-left">
|
||||
<SNRView ref="snrViewInstance" />
|
||||
</div>
|
||||
<div class="text-center w-1/2 ml-auto">
|
||||
<PlanisphereView ref="planisphereViewInstance" />
|
||||
</div>
|
||||
<div class="text-left">
|
||||
<NMEAView ref="nmeaViewInstance" />
|
||||
</div>
|
||||
<div class="text-center w-1/2 ml-auto">
|
||||
<BaseInfoView ref="baseInfoViewInstance" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import SNRView from './components/SNRView.vue'
|
||||
import NMEAView from './components/NMEAView.vue'
|
||||
import EarthView from './components/EarthView.vue'
|
||||
import BaseInfoView from './components/BaseInfoView.vue'
|
||||
import PlanisphereView from './components/PlanisphereView.vue'
|
||||
|
||||
|
||||
const nmeaViewInstance = ref(null)
|
||||
const snrViewInstance = ref(null)
|
||||
const earthViewInstance = ref(null)
|
||||
const baseInfoViewInstance = ref(null)
|
||||
const planisphereViewInstance = ref(null)
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
if ('ipcRenderer' in window) {
|
||||
ipcRenderer.receive('NMEA_RECEIVED', nmeaViewInstance.value.update)
|
||||
|
||||
ipcRenderer.receive('NMEA_HANDLED' , (nmeaStr: string) => {
|
||||
const nmea = JSON.parse(nmeaStr)
|
||||
|
||||
snrViewInstance.value.update(nmea)
|
||||
earthViewInstance.value.update(nmea)
|
||||
baseInfoViewInstance.value.update(nmea)
|
||||
planisphereViewInstance.value.update(nmea)
|
||||
})
|
||||
|
||||
ipcRenderer.send('APP_MOUNTED')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.front-container {
|
||||
grid-template-rows: 55vh;
|
||||
background: linear-gradient(to right, rgba(17, 24, 39, 0.8) , transparent, rgba(17, 24, 39, 0.8));
|
||||
}
|
||||
</style>
|
||||
113
src/render/api/util.js
Normal file
113
src/render/api/util.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Helpers } from 'nmea'
|
||||
|
||||
const getPosition = (nmea) => {
|
||||
if (!nmea) return
|
||||
|
||||
const { RMC } = nmea
|
||||
const position = { lng: Helpers.parseLongitude(RMC.lon, RMC.lonPole), lat: Helpers.parseLatitude(RMC.lat, RMC.latPole) }
|
||||
|
||||
return position
|
||||
}
|
||||
|
||||
const getBaseInfo = (nmea) => {
|
||||
if (!nmea) return
|
||||
|
||||
const { RMC, GGA } = nmea
|
||||
try {
|
||||
const longitude = (!RMC.lon || !RMC.lonPole) ? '' : Helpers.parseLongitude(RMC.lon, RMC.lonPole)
|
||||
const latitude = (!RMC.lat || !RMC.latPole) ? '' : Helpers.parseLatitude(RMC.lat, RMC.latPole)
|
||||
const altitude = GGA.alt + GGA.altUnit
|
||||
const date = (!RMC.date) ? '' : '20' + RMC.date.slice(4, 6) + '-' + RMC.date.slice(2, 4) + '-' + RMC.date.slice(0, 2)
|
||||
let UTCTime = '', BJTime = ''
|
||||
if (RMC.timestamp) {
|
||||
const hour = parseInt(RMC.timestamp.slice(0, 2))
|
||||
const bjHour = (hour + 8) % 24
|
||||
UTCTime = hour.toString().padStart(2, '0') + ':' + RMC.timestamp.slice(2, 4) + ':' + RMC.timestamp.slice(4, 6)
|
||||
BJTime = bjHour.toString().padStart(2, '0') + ':' + RMC.timestamp.slice(2, 4) + ':' + RMC.timestamp.slice(4, 6)
|
||||
}
|
||||
|
||||
const baseInfo = { longitude, latitude, altitude, date, UTCTime, BJTime }
|
||||
return baseInfo
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const getSNROption = (nmea) => {
|
||||
if (!nmea) return
|
||||
|
||||
const { GSV, GSA } = nmea
|
||||
const GSVArr = GSV.filter((gsv) => gsv.talker_id === 'BD')
|
||||
const GSAArr = GSA.filter((gsa) => gsa.talker_id === 'BD')
|
||||
|
||||
try {
|
||||
let yAxis_data = [], series_data = []
|
||||
const in_positioning_id = get_in_positioning_id(GSAArr)
|
||||
|
||||
GSVArr.forEach(({ satellites }) => {
|
||||
satellites.forEach(({ id, SNRdB }) => {
|
||||
yAxis_data.push(id)
|
||||
if (in_positioning_id.indexOf(parseInt(id)) < 0) {
|
||||
series_data.push([SNRdB, id, false])
|
||||
} else {
|
||||
series_data.push([SNRdB, id, true])
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const SNROption = {
|
||||
yAxis: {
|
||||
data: yAxis_data
|
||||
},
|
||||
series: [{
|
||||
data: series_data
|
||||
}]
|
||||
}
|
||||
|
||||
return SNROption
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const getPlanisphereOption = (nmea) => {
|
||||
if (!nmea) return
|
||||
|
||||
const { GSV, GSA } = nmea
|
||||
const GSVArr = GSV.filter((gsv) => gsv.talker_id === 'BD')
|
||||
const GSAArr = GSA.filter((gsa) => gsa.talker_id === 'BD')
|
||||
|
||||
try {
|
||||
let data = []
|
||||
const in_positioning_id = get_in_positioning_id(GSAArr)
|
||||
|
||||
GSVArr.forEach(({ satellites }) => {
|
||||
satellites.forEach(({ id, elevationDeg, azimuthTrue }) => {
|
||||
if (in_positioning_id.indexOf(parseInt(id)) < 0) {
|
||||
data.push([parseInt(elevationDeg), azimuthTrue, id, false])
|
||||
} else {
|
||||
data.push([parseInt(elevationDeg), azimuthTrue, id, true])
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const planisphereOption = {
|
||||
series: [{ data }]
|
||||
}
|
||||
|
||||
return planisphereOption
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const get_in_positioning_id = (GSAArr) => {
|
||||
let in_positioning_id = []
|
||||
GSAArr.forEach(({ satellites }) => {
|
||||
in_positioning_id.push(...satellites)
|
||||
});
|
||||
|
||||
return in_positioning_id
|
||||
}
|
||||
|
||||
export { getPosition, getBaseInfo, getSNROption, getPlanisphereOption }
|
||||
BIN
src/render/assets/earth.jpg
Normal file
BIN
src/render/assets/earth.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
src/render/assets/pin.png
Normal file
BIN
src/render/assets/pin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
91
src/render/components/BaseInfoView.vue
Normal file
91
src/render/components/BaseInfoView.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div>
|
||||
<SubTitleView :title="'基础信息'" />
|
||||
<div class="text-center text-2xl leading-loose text-blue-100">
|
||||
<div>
|
||||
<div class="label">经 度:</div>
|
||||
<div class="content">{{ baseInfo.longitude ? baseInfo.longitude : '' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">纬 度:</div>
|
||||
<div class="content">{{ baseInfo.latitude ? baseInfo.latitude : '' }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">海 拔:</div>
|
||||
<div class="content">{{ baseInfo.altitude }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">日 期:</div>
|
||||
<div class="content">{{ baseInfo.date }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">UTC时间:</div>
|
||||
<div class="content">{{ baseInfo.UTCTime }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">北京时间:</div>
|
||||
<div class="content">{{ baseInfo.BJTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue'
|
||||
import SubTitleView from './SubTitleView.vue'
|
||||
import { getBaseInfo } from '../api/util'
|
||||
|
||||
|
||||
let baseInfo = reactive({
|
||||
longitude : '',
|
||||
latitude : '',
|
||||
altitude : '',
|
||||
date : '',
|
||||
UTCTime : '',
|
||||
BJTime : '',
|
||||
})
|
||||
|
||||
let longitudeD = ref()
|
||||
let longitudeM = ref()
|
||||
let longitudeS = ref()
|
||||
|
||||
let latitudeD = ref()
|
||||
let latitudeM = ref()
|
||||
let latitudeS = ref()
|
||||
|
||||
const update = (nmea) => {
|
||||
const newBaseInfo = getBaseInfo(nmea)
|
||||
if (!newBaseInfo) return
|
||||
|
||||
Object.assign(baseInfo, newBaseInfo)
|
||||
|
||||
longitudeD.value = baseInfo.longitude.substring(0,baseInfo.longitude.indexOf("."))+'°'
|
||||
longitudeM.value = String((Number('0.'+baseInfo.longitude.substring(baseInfo.longitude.indexOf(".")+1,baseInfo.longitude.length))*60).toFixed(10)).substring(0,String((Number('0.'+baseInfo.longitude.substring(baseInfo.longitude.indexOf(".")+1,baseInfo.longitude.length))*60).toFixed(10)).indexOf("."))+'′'
|
||||
longitudeS.value = (Number('0.'+(Number('0.'+baseInfo.longitude.substring(baseInfo.longitude.indexOf(".")+1,baseInfo.longitude.length))*60).toFixed(10).substring(String((Number('0.'+baseInfo.longitude.substring(baseInfo.longitude.indexOf(".")+1,baseInfo.longitude.length))*60).toFixed(10)).indexOf(".")+1,((Number('0.'+baseInfo.longitude.substring(baseInfo.longitude.indexOf(".")+1,baseInfo.longitude.length))*60).toFixed(10)).length))*60).toFixed(6)+'″'
|
||||
|
||||
latitudeD.value = baseInfo.latitude.substring(0,baseInfo.latitude.indexOf("."))+'°'
|
||||
latitudeM.value = String((Number('0.'+baseInfo.latitude.substring(baseInfo.latitude.indexOf(".")+1,baseInfo.latitude.length))*60).toFixed(10)).substring(0,String((Number('0.'+baseInfo.latitude.substring(baseInfo.latitude.indexOf(".")+1,baseInfo.latitude.length))*60).toFixed(10)).indexOf("."))+'′'
|
||||
latitudeS.value = (Number('0.'+(Number('0.'+baseInfo.latitude.substring(baseInfo.latitude.indexOf(".")+1,baseInfo.latitude.length))*60).toFixed(10).substring(String((Number('0.'+baseInfo.latitude.substring(baseInfo.latitude.indexOf(".")+1,baseInfo.latitude.length))*60).toFixed(10)).indexOf(".")+1,((Number('0.'+baseInfo.latitude.substring(baseInfo.latitude.indexOf(".")+1,baseInfo.latitude.length))*60).toFixed(10)).length))*60).toFixed(6)+'″'
|
||||
}
|
||||
|
||||
defineExpose({ update })
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@layer components {
|
||||
.label {
|
||||
@apply float-left text-right w-2/5;
|
||||
min-width: 120px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.content {
|
||||
@apply float-left text-left w-3/5;
|
||||
min-width: 165px;
|
||||
height: 36px;
|
||||
/* min-height: 48px; */
|
||||
}
|
||||
}
|
||||
</style>
|
||||
58
src/render/components/EarthView.vue
Normal file
58
src/render/components/EarthView.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<vc-viewer :shouldAnimate="true" :showCredit="false" :infoBox="false" @ready="onViewerReady">
|
||||
<vc-layer-imagery>
|
||||
<vc-provider-imagery-singletile :url="earth" />
|
||||
</vc-layer-imagery>
|
||||
|
||||
<vc-entity :show="show" :position="position">
|
||||
<vc-graphics-billboard :image="pin" :verticalOrigin="1" />
|
||||
</vc-entity>
|
||||
</vc-viewer>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, computed } from 'vue'
|
||||
import { getPosition } from '../api/util'
|
||||
import pin from '../assets/pin.png'
|
||||
import earth from '../assets/earth.jpg'
|
||||
|
||||
const position = reactive({ lng: NaN, lat: NaN })
|
||||
const show = computed(() => {
|
||||
const { lng, lat } = position
|
||||
|
||||
if (isNaN(lng) || lng > 180 || lng < -180) return false
|
||||
if (isNaN(lat) || lat > 90 || lat < -90 ) return false
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// 设置地球自转动画效果
|
||||
const onViewerReady = ({ Cesium, viewer }) => {
|
||||
const { JulianDate, Cartesian3 } = Cesium
|
||||
const { clock, scene } = viewer
|
||||
|
||||
let prev = clock.currentTime
|
||||
clock.onTick.addEventListener(() => {
|
||||
const current = clock.currentTime
|
||||
|
||||
const interval = JulianDate.toDate(current) - JulianDate.toDate(prev)
|
||||
prev = current
|
||||
|
||||
scene.camera.rotate(
|
||||
Cartesian3.UNIT_Z,
|
||||
(Math.PI / (24 * 60 * 60)) * interval * 1,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const update = (nmea) => {
|
||||
const newPosition = getPosition(nmea)
|
||||
if (!newPosition) return
|
||||
|
||||
Object.assign(position, newPosition)
|
||||
}
|
||||
|
||||
defineExpose({ update })
|
||||
</script>
|
||||
20
src/render/components/NMEAView.vue
Normal file
20
src/render/components/NMEAView.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div>
|
||||
<SubTitleView :title="'NMEA'" />
|
||||
|
||||
<div ref="nmeaContainer" class="whitespace-pre-line text-blue-100 text-sm">
|
||||
{{ nmeaStr }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import SubTitleView from './SubTitleView.vue'
|
||||
|
||||
const nmeaStr = ref('')
|
||||
const update = (value) => nmeaStr.value = value
|
||||
|
||||
defineExpose({ update })
|
||||
</script>
|
||||
86
src/render/components/PlanisphereView.vue
Normal file
86
src/render/components/PlanisphereView.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<SubTitleView :title="'星位视图'" />
|
||||
<div @dblclick="close" class="h-4/5">
|
||||
<v-chart :option="option" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { use } from "echarts/core";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { ScatterChart } from "echarts/charts";
|
||||
import { PolarComponent } from "echarts/components";
|
||||
import VChart from "vue-echarts";
|
||||
import { reactive } from "vue";
|
||||
import SubTitleView from './SubTitleView.vue'
|
||||
import { getPlanisphereOption } from '../api/util'
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
ScatterChart,
|
||||
PolarComponent
|
||||
]);
|
||||
|
||||
const LOCATED_COLOR = '#e5323e'
|
||||
const UNLOCATED_COLOR = '#9ca3af'
|
||||
|
||||
|
||||
const option = reactive({
|
||||
polar: {},
|
||||
radiusAxis: {
|
||||
inverse: true,
|
||||
min: 0,
|
||||
max: 90,
|
||||
axisLabel: {
|
||||
rotate: -25,
|
||||
showMinLabel: false,
|
||||
showMaxLabel: false,
|
||||
verticalAlign: 'bottom'
|
||||
}
|
||||
},
|
||||
angleAxis: {
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 360,
|
||||
axisTick: {show: false},
|
||||
axisLabel: {
|
||||
formatter: function (value) {
|
||||
switch (value)
|
||||
{
|
||||
case 0 : return 'N';
|
||||
case 90 : return 'E';
|
||||
case 180 : return 'S';
|
||||
case 270 : return 'W';
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
coordinateSystem: 'polar',
|
||||
type: 'scatter',
|
||||
symbolSize: 32,
|
||||
label: {
|
||||
show: true,
|
||||
formatter: '{@[2]}'
|
||||
},
|
||||
itemStyle: {
|
||||
color: ({ value }) => {
|
||||
return value[3] ? LOCATED_COLOR : UNLOCATED_COLOR
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
const update = (nmea) => {
|
||||
const newOption = getPlanisphereOption(nmea)
|
||||
if (!newOption) return
|
||||
|
||||
Object.assign(option, newOption)
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
window.ipcRenderer.send('CLOSE')
|
||||
}
|
||||
|
||||
defineExpose({ update })
|
||||
</script>
|
||||
78
src/render/components/SNRView.vue
Normal file
78
src/render/components/SNRView.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<SubTitleView :title="'载噪比'" />
|
||||
<div class="w-2/3 h-4/5">
|
||||
<v-chart :option="option" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { use, graphic } from "echarts/core";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import { BarChart } from "echarts/charts";
|
||||
import VChart from "vue-echarts";
|
||||
import { reactive } from "vue";
|
||||
import SubTitleView from './SubTitleView.vue'
|
||||
import { getSNROption } from '../api/util'
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
BarChart
|
||||
]);
|
||||
|
||||
const LOCATED_COLOR = new graphic.LinearGradient(
|
||||
0, 0, 1, 0,
|
||||
[
|
||||
{offset: 0.3, color: '#83bff6'},
|
||||
{offset: 1, color: '#188df0'}
|
||||
]
|
||||
)
|
||||
const UNLOCATED_COLOR = '#9ca3af'
|
||||
|
||||
const option = reactive({
|
||||
grid: {
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
containLabel: true
|
||||
},
|
||||
xAxis: {
|
||||
show: false,
|
||||
type: 'value',
|
||||
min: 0,
|
||||
max: 60,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
axisLine: {show: false},
|
||||
axisTick: {show: false},
|
||||
axisLabel: {color: 'white'}
|
||||
},
|
||||
series: [{
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
position: 'insideRight',
|
||||
formatter: function ({ value }) {
|
||||
return value[0] <= 5 ? '' : value[0];
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
color: ({ value }) => {
|
||||
return value[2] ? LOCATED_COLOR : UNLOCATED_COLOR
|
||||
}
|
||||
},
|
||||
barMaxWidth: 24
|
||||
}]
|
||||
})
|
||||
|
||||
const update = (nmea) => {
|
||||
const newOption = getSNROption(nmea)
|
||||
if (!newOption) return
|
||||
|
||||
Object.assign(option, newOption)
|
||||
}
|
||||
|
||||
defineExpose({ update })
|
||||
</script>
|
||||
9
src/render/components/SubTitleView.vue
Normal file
9
src/render/components/SubTitleView.vue
Normal file
@@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<h1 class="text-4xl text-yellow-50 mb-5"> {{ title }} </h1>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
title: String
|
||||
})
|
||||
</script>
|
||||
9
src/render/main.css
Normal file
9
src/render/main.css
Normal file
@@ -0,0 +1,9 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.w-h-full {
|
||||
@apply w-full h-full m-0 p-0;
|
||||
}
|
||||
}
|
||||
15
src/render/main.ts
Normal file
15
src/render/main.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
import 'vue-cesium/dist/index.css'
|
||||
import { VcViewer, VcEntity, VcLayerImagery, VcProviderImagerySingletile, VcGraphicsBillboard } from 'vue-cesium'
|
||||
|
||||
import './main.css'
|
||||
|
||||
const cesiumPath = (process.env.NODE_ENV === 'development' ? './node_modules/cesium/Build/Cesium/Cesium.js': './Cesium/Cesium.js')
|
||||
|
||||
const app = createApp(App)
|
||||
// 局部引入VueCesium
|
||||
app.use(VcViewer).use(VcEntity).use(VcLayerImagery).use(VcProviderImagerySingletile).use(VcGraphicsBillboard)
|
||||
app.config.globalProperties.$VueCesium = { cesiumPath }
|
||||
app.mount('#app')
|
||||
5
src/render/shims-vue.d.ts
vendored
Normal file
5
src/render/shims-vue.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
Reference in New Issue
Block a user