Sats
This commit is contained in:
@@ -32,11 +32,13 @@ const showWind = ref(false)
|
||||
const windSrc = ref('')
|
||||
|
||||
const OVERLAYS = [
|
||||
{ id: 'traffic', label: 'Traffic', icon: '✈️' },
|
||||
{ id: 'sats', label: 'Sats', icon: '🛰️' },
|
||||
{ id: 'rain', label: 'Rain', icon: '🌧️' },
|
||||
{ id: 'wind', label: 'Wind', icon: '💨' },
|
||||
]
|
||||
|
||||
const activeOverlays = ref<Set<string>>(new Set(['rain']))
|
||||
const activeOverlays = ref<Set<string>>(new Set(['traffic', 'rain']))
|
||||
const overlayLayers: { [key: string]: any } = {}
|
||||
|
||||
let map: Map
|
||||
@@ -108,20 +110,21 @@ async function refreshRain() {
|
||||
map.addLayer(layer)
|
||||
}
|
||||
|
||||
function toggleAT() {
|
||||
if (atActive.value) {
|
||||
aisLayer.hide()
|
||||
airTraffic.hide()
|
||||
range.hide()
|
||||
} else {
|
||||
aisLayer.show()
|
||||
airTraffic.show()
|
||||
range.show()
|
||||
}
|
||||
atActive.value = !atActive.value
|
||||
}
|
||||
|
||||
function toggleOverlay(id: string) {
|
||||
if (id === 'traffic') {
|
||||
if (activeOverlays.value.has('traffic')) {
|
||||
activeOverlays.value.delete('traffic');
|
||||
aisLayer.hide()
|
||||
airTraffic.hide()
|
||||
range.hide()
|
||||
} else {
|
||||
activeOverlays.value.add('traffic');
|
||||
aisLayer.show()
|
||||
airTraffic.show()
|
||||
range.show()
|
||||
}
|
||||
}
|
||||
|
||||
if (id === 'wind') {
|
||||
if (activeOverlays.value.has('wind')) {
|
||||
activeOverlays.value.delete('wind')
|
||||
@@ -340,9 +343,6 @@ watch(() => props.dark, dark => {
|
||||
/>
|
||||
|
||||
<div class="overlay-toggles desktop">
|
||||
<button class="overlay-btn" :class="{ active: atActive }" @click="toggleAT">
|
||||
✈️ Traffic
|
||||
</button>
|
||||
<button
|
||||
v-for="o in OVERLAYS" :key="o.id"
|
||||
class="overlay-btn"
|
||||
@@ -358,9 +358,6 @@ watch(() => props.dark, dark => {
|
||||
⚙️ Layers
|
||||
</button>
|
||||
<div v-if="showOverlays" class="layers-dropdown">
|
||||
<button class="overlay-btn" :class="{ active: atActive }" @click="toggleAT">
|
||||
✈️ Traffic
|
||||
</button>
|
||||
<button
|
||||
v-for="o in OVERLAYS" :key="o.id"
|
||||
class="overlay-btn"
|
||||
|
||||
48
client/src/components/Satellite.vue
Normal file
48
client/src/components/Satellite.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const emit = defineEmits(['bringToFront']);
|
||||
const props = defineProps<{ history: any[]; position: { x: number, y: number }; onClose: () => void }>()
|
||||
const latest = computed(() => props.history[props.history.length - 1])
|
||||
|
||||
/** Center = zenith (el 90), edge = horizon (el 0). */
|
||||
function polarPoint(az: number, el: number, radius = 100) {
|
||||
const r = radius * (1 - el / 90)
|
||||
const rad = (az - 90) * Math.PI / 180
|
||||
return { x: 120 + r * Math.cos(rad), y: 120 + r * Math.sin(rad) }
|
||||
}
|
||||
|
||||
const trackPoints = computed(() => props.history.map(r => polarPoint(r.az, r.el)).map(p => `${p.x},${p.y}`).join(' '))
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tinygs-popup { position: fixed; width: 340px; background: var(--surface); border-radius: 12px; padding: 12px; box-shadow: 0 4px 20px rgba(0,0,0,.3); z-index: 1000; }
|
||||
.close { position: absolute; top: 8px; right: 8px; background: none; border: none; cursor: pointer; }
|
||||
.fields { width: 100%; font-size: 13px; margin-bottom: 8px; td { padding: 2px 4px; } }
|
||||
.ok { color: #3fdb6d; }
|
||||
.bad { color: #e0475a; }
|
||||
.radar { width: 100%; }
|
||||
.ring, .axis { fill: none; stroke: var(--border); stroke-width: 1; }
|
||||
.track { fill: none; stroke: #d4a4ff; stroke-width: 1.5; }
|
||||
.current { fill: #d4a4ff; }
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="tinygs-popup" :style="{ left: position.x + 'px', top: position.y + 'px' }" @mousedown="emit('bringToFront')">
|
||||
<button class="close" @click="onClose">✕</button>
|
||||
<h3>{{ latest?.satellite || 'Unknown' }}</h3>
|
||||
<table class="fields">
|
||||
<tr><td>Frequency</td><td>{{ latest?.freqMHz }} MHz</td></tr>
|
||||
<tr><td>Packet RSSI</td><td>{{ latest?.packetRssi }} dBm</td></tr>
|
||||
<tr><td>SNR</td><td>{{ latest?.packetSnr }} dB</td></tr>
|
||||
<tr><td>Freq error</td><td>{{ latest?.freqError }} Hz</td></tr>
|
||||
<tr><td>CRC</td><td :class="latest?.crcOk ? 'ok' : 'bad'">{{ latest?.crcOk ? 'OK' : 'ERROR' }}</td></tr>
|
||||
</table>
|
||||
<svg viewBox="0 0 240 240" class="radar">
|
||||
<circle cx="120" cy="120" r="100" class="ring" /><circle cx="120" cy="120" r="66" class="ring" /><circle cx="120" cy="120" r="33" class="ring" />
|
||||
<line x1="120" y1="20" x2="120" y2="220" class="axis" /><line x1="20" y1="120" x2="220" y2="120" class="axis" />
|
||||
<polyline :points="trackPoints" class="track" />
|
||||
<circle v-if="latest" v-bind="polarPoint(latest.az, latest.el)" r="4" class="current" />
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
@@ -79,11 +79,11 @@ function drawTrace(ctx: CanvasRenderingContext2D, t: (typeof traces)[number], co
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
for (let i = 0; i < settledCount; i++) {
|
||||
const p = buf[i];
|
||||
const p = <any>buf[i];
|
||||
i === 0 ? ctx.moveTo(p.x + slide, p.y) : ctx.lineTo(p.x + slide, p.y);
|
||||
}
|
||||
|
||||
const animPoints = [anchor, ...buf.slice(settledCount)];
|
||||
const animPoints = <any>[anchor, ...buf.slice(settledCount)];
|
||||
|
||||
for (let i = 1; i <= animFloor && i < animPoints.length; i++) {
|
||||
ctx.lineTo(animPoints[i].x + slide, animPoints[i].y);
|
||||
@@ -116,9 +116,9 @@ function draw(timestamp: number) {
|
||||
|
||||
// Axis label — top-left of each band
|
||||
ctx.font = '16px monospace';
|
||||
ctx.fillStyle = AXES[i].color;
|
||||
ctx.fillStyle = (<any>AXES)[i].color;
|
||||
ctx.globalAlpha = 0.6;
|
||||
ctx.fillText(AXES[i].label, W - 20, i * BAND + 14);
|
||||
ctx.fillText((<any>AXES)[i].label, W - 20, i * BAND + 14);
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
// Center line per band — full width
|
||||
@@ -129,7 +129,7 @@ function draw(timestamp: number) {
|
||||
ctx.lineTo(W, mid);
|
||||
ctx.stroke();
|
||||
|
||||
drawTrace(ctx, traces[i], AXES[i].color);
|
||||
drawTrace(ctx, <any>traces[i], (<any>AXES)[i].color);
|
||||
});
|
||||
|
||||
rafId = requestAnimationFrame(draw);
|
||||
@@ -138,7 +138,7 @@ function draw(timestamp: number) {
|
||||
async function poll() {
|
||||
d.value = await api.current('seismic_magnitude,seismic_x,seismic_y,seismic_z');
|
||||
AXES.forEach((axis, i) => {
|
||||
const t = traces[i];
|
||||
const t = <any>traces[i];
|
||||
t.history.push(d.value[axis.key] ?? 0);
|
||||
if (t.history.length > MAX_PTS) t.history.shift();
|
||||
buildBuffer(t, i);
|
||||
|
||||
Reference in New Issue
Block a user