Finished Sonde support

This commit is contained in:
2026-09-13 14:04:03 -04:00
parent 1d1e7351db
commit 8eb3e49308
5 changed files with 124 additions and 76 deletions

View File

@@ -5,7 +5,9 @@ const props = withDefaults(defineProps<{
history: any[] history: any[]
currentAltitude: number | null currentAltitude: number | null
color?: string, color?: string,
unit: string unit: string,
startTime?: string,
endTime?: string,
}>(), { }>(), {
color: '#0f0' color: '#0f0'
}); });
@@ -153,11 +155,11 @@ const graph = computed(() => {
/> />
<text :x="PAD.left" :y="HEIGHT - 6"> <text :x="PAD.left" :y="HEIGHT - 6">
{{ graph.startTime }} {{ startTime || graph.startTime }}
</text> </text>
<text :x="WIDTH - PAD.right" :y="HEIGHT - 6" text-anchor="end"> <text :x="WIDTH - PAD.right" :y="HEIGHT - 6" text-anchor="end">
{{ graph.endTime }} {{ endTime || graph.endTime }}
</text> </text>
</svg> </svg>
</div> </div>

View File

@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import {formatDate} from '@ztimson/utils';
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted } from 'vue'
import Altitude from '@/components/Altitude.vue' import Altitude from '@/components/Altitude.vue'
@@ -288,7 +289,7 @@ onUnmounted(() => {
} }
.tp-gauges { .tp-gauges {
flex: 1; flex: 0 0 110px;
} }
.tp-gauge-wrap { .tp-gauge-wrap {
@@ -330,21 +331,66 @@ onUnmounted(() => {
margin-bottom: 0.25em; margin-bottom: 0.25em;
} }
.tp-fields { .tp-environment {
width: 100%; flex: 1;
font-size: 11px; min-width: 0;
padding: 0 16px 12px;
border-collapse: collapse;
} }
.tp-fields td { .tp-environment-label {
padding: 2px 4px;
color: #888; color: #888;
font-size: 11px;
font-weight: bold;
margin-bottom: 3px;
} }
.tp-fields td:last-child { .tp-environment-cards {
color: #ccc; display: flex;
text-align: right; 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 { .ok {
@@ -437,35 +483,49 @@ onUnmounted(() => {
</div> </div>
<table class="tp-fields"> <div class="tp-environment">
<tbody> <div class="tp-environment-label">Environment</div>
<tr>
<td>Heading</td>
<td>{{ latest?.heading?.toFixed(1) ?? '—' }}°</td>
</tr>
<tr> <div class="tp-environment-cards gap-4">
<td>Temperature</td> <div class="tp-environment-card">
<td>{{ latest?.temperature ?? '—' }} °C</td> <span class="tp-environment-icon">🌡</span>
</tr> <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> <div class="tp-environment-card">
<td>Humidity</td> <span class="tp-environment-icon">💧</span>
<td>{{ latest?.humidity ?? '—' }} %</td> <div class="tp-environment-info">
</tr> <span class="tp-environment-stat-label">Humidity</span>
<span class="tp-environment-stat-value">
{{ latest?.humidity ?? '—' }} %
</span>
</div>
</div>
<tr> <div class="tp-environment-card">
<td>PPM</td> <span class="tp-environment-icon">🫧</span>
<td>{{ latest?.ppm?.toFixed(2) ?? '—' }}</td> <div class="tp-environment-info">
</tr> <span class="tp-environment-stat-label">PPM</span>
</tbody> <span class="tp-environment-stat-value">
</table> {{ latest?.ppm?.toFixed(2) ?? '—' }}
</span>
</div>
</div>
</div>
</div>
</div> </div>
<Altitude <Altitude
:history="altitudeHistory" :history="altitudeHistory"
:currentAltitude="currentAltitude" :currentAltitude="currentAltitude"
:unit="altitudeUnit.label" :unit="altitudeUnit.label"
start-time=" "
:end-time="formatDate('HH:MM A', latest.timestamp)"
color="#ffaa00" color="#ffaa00"
/> />

View File

@@ -16,6 +16,7 @@ const EARTH_R = 6371;
const AUTO_ELEVATION = 70; const AUTO_ELEVATION = 70;
interface TinyGSReading { interface TinyGSReading {
id: number;
timestamp: number; timestamp: number;
freqMHz: number; freqMHz: number;
satellite: string; satellite: string;
@@ -137,7 +138,7 @@ export class SatsLayer {
this.clickHandler = null; 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); if (this.layer) this.map.removeLayer(this.layer);
} }
@@ -199,16 +200,16 @@ export class SatsLayer {
return this._calcRange(sat)?.slantRange ?? null; return this._calcRange(sat)?.slantRange ?? null;
} }
getById(name: string): TinyGSSatellite | null { getById(id: number): TinyGSSatellite | null {
return this.data.find(s => s.satellite === name) ?? null; return this.data.find(s => s.id === id) ?? null;
} }
openAutoPopup(sat: TinyGSSatellite) { openAutoPopup(sat: TinyGSSatellite) {
this._openPopup(sat); this._openPopup(sat);
} }
closeAutoPopup(name: string) { closeAutoPopup(id: number) {
this._closePopup(name); this._closePopup(id);
} }
private async _fetch() { private async _fetch() {
@@ -238,7 +239,7 @@ export class SatsLayer {
} }
const marker = new Feature({geometry: new Point(points[points.length - 1])}); 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.set('satData', sat);
marker.setStyle(new Style({ marker.setStyle(new Style({
image: new Icon({ image: new Icon({
@@ -258,7 +259,7 @@ export class SatsLayer {
} }
private _drawReceptionCircle(sat: TinyGSSatellite, radiusKm?: number | null) { private _drawReceptionCircle(sat: TinyGSSatellite, radiusKm?: number | null) {
this._removeReceptionCircle(sat.satellite); this._removeReceptionCircle(sat.id);
if (!radiusKm) return; if (!radiusKm) return;
const feature = new Feature({ const feature = new Feature({
@@ -279,20 +280,20 @@ export class SatsLayer {
}); });
this.map.addLayer(layer); this.map.addLayer(layer);
this.receptionCircles[sat.satellite] = layer; this.receptionCircles[sat.id] = layer;
} }
private _removeReceptionCircle(name: string) { private _removeReceptionCircle(id: number) {
const layer = this.receptionCircles[name]; const layer = this.receptionCircles[id];
if (layer) { if (layer) {
this.map.removeLayer(layer); this.map.removeLayer(layer);
delete this.receptionCircles[name]; delete this.receptionCircles[id];
} }
} }
private _openPopup(sat: TinyGSSatellite) { private _openPopup(sat: TinyGSSatellite) {
if (this.popups[sat.satellite]) return; if (this.popups[sat.id]) return;
const history = [...sat.history, sat]; const history = [...sat.history, sat];
const range = this._calcRange(sat); const range = this._calcRange(sat);
@@ -311,13 +312,13 @@ export class SatsLayer {
position: mobile position: mobile
? {x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16} ? {x: window.innerWidth / 2 - 180, y: window.innerHeight - 540 - 16}
: {x: 16, y: 16}, : {x: 16, y: 16},
onClose: () => this._closePopup(sat.satellite), onClose: () => this._closePopup(sat.id),
onBringToFront: () => bringToFront(container), onBringToFront: () => bringToFront(container),
}); });
vueRender(h(TinyGSPopup, makeProps(history, range, eta)), container); vueRender(h(TinyGSPopup, makeProps(history, range, eta)), container);
this.popups[sat.satellite] = { this.popups[sat.id] = {
container, container,
update: (h_: TinyGSReading[], r: RangeEstimate | null, e: number | null) => vueRender(h(TinyGSPopup, makeProps(h_, r, e)), container), update: (h_: TinyGSReading[], r: RangeEstimate | null, e: number | null) => vueRender(h(TinyGSPopup, makeProps(h_, r, e)), container),
unmount: () => { unmount: () => {
@@ -327,21 +328,18 @@ export class SatsLayer {
}; };
} }
private _closePopup(name: string) { private _closePopup(id: number) {
const popup = this.popups[name]; const popup = this.popups[id];
if(popup) {
if (popup) {
popup.unmount(); popup.unmount();
delete this.popups[name]; delete this.popups[id];
} }
this._removeReceptionCircle(id);
this._removeReceptionCircle(name);
} }
private _refreshPopups() { private _refreshPopups() {
for (const name of Object.keys(this.popups)) { for (const name of Object.keys(this.popups).map(Number)) {
const sat = this.data.find(s => s.satellite === name); const sat = this.data.find(s => s.id == name);
if (!sat) { if (!sat) {
this._closePopup(name); this._closePopup(name);
continue; continue;
@@ -349,13 +347,7 @@ export class SatsLayer {
const history = [...sat.history, sat]; const history = [...sat.history, sat];
const range = this._calcRange(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); this._drawReceptionCircle(sat, range?.horizonRadius);
} }
} }
@@ -364,14 +356,8 @@ export class SatsLayer {
this.clickHandler = evt => { this.clickHandler = evt => {
const f = this.map.forEachFeatureAtPixel(evt.pixel, f => f.get('satData') ? f : null); const f = this.map.forEachFeatureAtPixel(evt.pixel, f => f.get('satData') ? f : null);
if (!f) return; if (!f) return;
const sat = f.get('satData') as TinyGSSatellite; const sat = f.get('satData') as TinyGSSatellite;
if(this.popups[sat.id]) return this._closePopup(sat.id);
if (this.popups[sat.satellite]) {
this._closePopup(sat.satellite);
return;
}
this._openPopup(sat); this._openPopup(sat);
}; };

View File

@@ -244,7 +244,6 @@ export const getTinyGSData = () => {
id: track.id, id: track.id,
satellite: track.name, satellite: track.name,
...latest, ...latest,
altitudeKm: null,
velocity: { velocity: {
v: track.state.groundSpeedKmh, v: track.state.groundSpeedKmh,
az: track.state.azVelocity, az: track.state.azVelocity,

File diff suppressed because one or more lines are too long