Finished Sonde support
This commit is contained in:
@@ -5,7 +5,9 @@ const props = withDefaults(defineProps<{
|
||||
history: any[]
|
||||
currentAltitude: number | null
|
||||
color?: string,
|
||||
unit: string
|
||||
unit: string,
|
||||
startTime?: string,
|
||||
endTime?: string,
|
||||
}>(), {
|
||||
color: '#0f0'
|
||||
});
|
||||
@@ -153,11 +155,11 @@ const graph = computed(() => {
|
||||
/>
|
||||
|
||||
<text :x="PAD.left" :y="HEIGHT - 6">
|
||||
{{ graph.startTime }}
|
||||
{{ startTime || graph.startTime }}
|
||||
</text>
|
||||
|
||||
<text :x="WIDTH - PAD.right" :y="HEIGHT - 6" text-anchor="end">
|
||||
{{ graph.endTime }}
|
||||
{{ endTime || graph.endTime }}
|
||||
</text>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import {formatDate} from '@ztimson/utils';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import Altitude from '@/components/Altitude.vue'
|
||||
|
||||
@@ -288,7 +289,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.tp-gauges {
|
||||
flex: 1;
|
||||
flex: 0 0 110px;
|
||||
}
|
||||
|
||||
.tp-gauge-wrap {
|
||||
@@ -330,21 +331,66 @@ onUnmounted(() => {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.tp-fields {
|
||||
width: 100%;
|
||||
font-size: 11px;
|
||||
padding: 0 16px 12px;
|
||||
border-collapse: collapse;
|
||||
.tp-environment {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tp-fields td {
|
||||
padding: 2px 4px;
|
||||
.tp-environment-label {
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.tp-fields td:last-child {
|
||||
color: #ccc;
|
||||
text-align: right;
|
||||
.tp-environment-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tp-environment-card {
|
||||
min-height: 42px;
|
||||
padding: 6px;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.tp-environment-icon {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
flex: 0 0 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255,170,0,0.08);
|
||||
border-radius: 3px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.tp-environment-info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.tp-environment-stat-label {
|
||||
color: #888;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tp-environment-stat-value {
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ok {
|
||||
@@ -437,35 +483,49 @@ onUnmounted(() => {
|
||||
|
||||
</div>
|
||||
|
||||
<table class="tp-fields">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Heading</td>
|
||||
<td>{{ latest?.heading?.toFixed(1) ?? '—' }}°</td>
|
||||
</tr>
|
||||
<div class="tp-environment">
|
||||
<div class="tp-environment-label">Environment</div>
|
||||
|
||||
<tr>
|
||||
<td>Temperature</td>
|
||||
<td>{{ latest?.temperature ?? '—' }} °C</td>
|
||||
</tr>
|
||||
<div class="tp-environment-cards gap-4">
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">🌡️</span>
|
||||
<div class="tp-environment-info">
|
||||
<span class="tp-environment-stat-label">Temperature</span>
|
||||
<span class="tp-environment-stat-value">
|
||||
{{ latest?.temperature ?? '—' }} °C
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td>Humidity</td>
|
||||
<td>{{ latest?.humidity ?? '—' }} %</td>
|
||||
</tr>
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">💧</span>
|
||||
<div class="tp-environment-info">
|
||||
<span class="tp-environment-stat-label">Humidity</span>
|
||||
<span class="tp-environment-stat-value">
|
||||
{{ latest?.humidity ?? '—' }} %
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
<td>PPM</td>
|
||||
<td>{{ latest?.ppm?.toFixed(2) ?? '—' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="tp-environment-card">
|
||||
<span class="tp-environment-icon">🫧</span>
|
||||
<div class="tp-environment-info">
|
||||
<span class="tp-environment-stat-label">PPM</span>
|
||||
<span class="tp-environment-stat-value">
|
||||
{{ latest?.ppm?.toFixed(2) ?? '—' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Altitude
|
||||
:history="altitudeHistory"
|
||||
:currentAltitude="currentAltitude"
|
||||
:unit="altitudeUnit.label"
|
||||
start-time=" "
|
||||
:end-time="formatDate('HH:MM A', latest.timestamp)"
|
||||
color="#ffaa00"
|
||||
/>
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ const EARTH_R = 6371;
|
||||
const AUTO_ELEVATION = 70;
|
||||
|
||||
interface TinyGSReading {
|
||||
id: number;
|
||||
timestamp: number;
|
||||
freqMHz: number;
|
||||
satellite: string;
|
||||
@@ -137,7 +138,7 @@ export class SatsLayer {
|
||||
this.clickHandler = null;
|
||||
}
|
||||
|
||||
for (const name of Object.keys(this.popups)) this._closePopup(name);
|
||||
for (const name of Object.keys(this.popups).map(Number)) this._closePopup(name);
|
||||
if (this.layer) this.map.removeLayer(this.layer);
|
||||
}
|
||||
|
||||
@@ -199,16 +200,16 @@ export class SatsLayer {
|
||||
return this._calcRange(sat)?.slantRange ?? null;
|
||||
}
|
||||
|
||||
getById(name: string): TinyGSSatellite | null {
|
||||
return this.data.find(s => s.satellite === name) ?? null;
|
||||
getById(id: number): TinyGSSatellite | null {
|
||||
return this.data.find(s => s.id === id) ?? null;
|
||||
}
|
||||
|
||||
openAutoPopup(sat: TinyGSSatellite) {
|
||||
this._openPopup(sat);
|
||||
}
|
||||
|
||||
closeAutoPopup(name: string) {
|
||||
this._closePopup(name);
|
||||
closeAutoPopup(id: number) {
|
||||
this._closePopup(id);
|
||||
}
|
||||
|
||||
private async _fetch() {
|
||||
@@ -238,7 +239,7 @@ export class SatsLayer {
|
||||
}
|
||||
|
||||
const marker = new Feature({geometry: new Point(points[points.length - 1])});
|
||||
marker.set('satellite', sat.satellite);
|
||||
marker.set('satellite', sat.id);
|
||||
marker.set('satData', sat);
|
||||
marker.setStyle(new Style({
|
||||
image: new Icon({
|
||||
@@ -258,7 +259,7 @@ export class SatsLayer {
|
||||
}
|
||||
|
||||
private _drawReceptionCircle(sat: TinyGSSatellite, radiusKm?: number | null) {
|
||||
this._removeReceptionCircle(sat.satellite);
|
||||
this._removeReceptionCircle(sat.id);
|
||||
if (!radiusKm) return;
|
||||
|
||||
const feature = new Feature({
|
||||
@@ -279,20 +280,20 @@ export class SatsLayer {
|
||||
});
|
||||
|
||||
this.map.addLayer(layer);
|
||||
this.receptionCircles[sat.satellite] = layer;
|
||||
this.receptionCircles[sat.id] = layer;
|
||||
}
|
||||
|
||||
private _removeReceptionCircle(name: string) {
|
||||
const layer = this.receptionCircles[name];
|
||||
private _removeReceptionCircle(id: number) {
|
||||
const layer = this.receptionCircles[id];
|
||||
|
||||
if (layer) {
|
||||
this.map.removeLayer(layer);
|
||||
delete this.receptionCircles[name];
|
||||
delete this.receptionCircles[id];
|
||||
}
|
||||
}
|
||||
|
||||
private _openPopup(sat: TinyGSSatellite) {
|
||||
if (this.popups[sat.satellite]) return;
|
||||
if (this.popups[sat.id]) return;
|
||||
|
||||
const history = [...sat.history, sat];
|
||||
const range = this._calcRange(sat);
|
||||
@@ -311,13 +312,13 @@ export class SatsLayer {
|
||||
position: mobile
|
||||
? {x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16}
|
||||
: {x: 16, y: 16},
|
||||
onClose: () => this._closePopup(sat.satellite),
|
||||
onClose: () => this._closePopup(sat.id),
|
||||
onBringToFront: () => bringToFront(container),
|
||||
});
|
||||
|
||||
vueRender(h(TinyGSPopup, makeProps(history, range, eta)), container);
|
||||
|
||||
this.popups[sat.satellite] = {
|
||||
this.popups[sat.id] = {
|
||||
container,
|
||||
update: (h_: TinyGSReading[], r: RangeEstimate | null, e: number | null) => vueRender(h(TinyGSPopup, makeProps(h_, r, e)), container),
|
||||
unmount: () => {
|
||||
@@ -327,21 +328,18 @@ export class SatsLayer {
|
||||
};
|
||||
}
|
||||
|
||||
private _closePopup(name: string) {
|
||||
const popup = this.popups[name];
|
||||
|
||||
if (popup) {
|
||||
private _closePopup(id: number) {
|
||||
const popup = this.popups[id];
|
||||
if(popup) {
|
||||
popup.unmount();
|
||||
delete this.popups[name];
|
||||
delete this.popups[id];
|
||||
}
|
||||
|
||||
this._removeReceptionCircle(name);
|
||||
this._removeReceptionCircle(id);
|
||||
}
|
||||
|
||||
private _refreshPopups() {
|
||||
for (const name of Object.keys(this.popups)) {
|
||||
const sat = this.data.find(s => s.satellite === name);
|
||||
|
||||
for (const name of Object.keys(this.popups).map(Number)) {
|
||||
const sat = this.data.find(s => s.id == name);
|
||||
if (!sat) {
|
||||
this._closePopup(name);
|
||||
continue;
|
||||
@@ -349,13 +347,7 @@ export class SatsLayer {
|
||||
|
||||
const history = [...sat.history, sat];
|
||||
const range = this._calcRange(sat);
|
||||
|
||||
this.popups[name].update(
|
||||
history,
|
||||
range,
|
||||
estimateEtaOut(history),
|
||||
);
|
||||
|
||||
this.popups[name].update(history, range, estimateEtaOut(history),);
|
||||
this._drawReceptionCircle(sat, range?.horizonRadius);
|
||||
}
|
||||
}
|
||||
@@ -364,14 +356,8 @@ export class SatsLayer {
|
||||
this.clickHandler = evt => {
|
||||
const f = this.map.forEachFeatureAtPixel(evt.pixel, f => f.get('satData') ? f : null);
|
||||
if (!f) return;
|
||||
|
||||
const sat = f.get('satData') as TinyGSSatellite;
|
||||
|
||||
if (this.popups[sat.satellite]) {
|
||||
this._closePopup(sat.satellite);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.popups[sat.id]) return this._closePopup(sat.id);
|
||||
this._openPopup(sat);
|
||||
};
|
||||
|
||||
|
||||
@@ -244,7 +244,6 @@ export const getTinyGSData = () => {
|
||||
id: track.id,
|
||||
satellite: track.name,
|
||||
...latest,
|
||||
altitudeKm: null,
|
||||
velocity: {
|
||||
v: track.state.groundSpeedKmh,
|
||||
az: track.state.azVelocity,
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user