157 lines
4.6 KiB
HTML
157 lines
4.6 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Fieldtest Positions</title>
|
|
|
|
<!-- Leaflet (swap to your local staged copies if preferred)
|
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
|
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> -->
|
|
<link rel="stylesheet" href="https://ryzdesk/lib/leaflet.css">
|
|
<script src="https://ryzdesk/lib/leaflet.js"></script>
|
|
|
|
<style>
|
|
html, body { height: 100%; margin: 0; }
|
|
#map { height: 100%; width: 100%; }
|
|
.legend {
|
|
position: absolute; z-index: 1000; background: rgba(255,255,255,0.85);
|
|
padding: 8px; margin: 10px; border-radius: 6px; font-family: sans-serif;
|
|
font-size: 13px;
|
|
}
|
|
.node-label {
|
|
background: rgba(255,255,255,0.85);
|
|
border: 1px solid rgba(0,0,0,0.25);
|
|
color: #000;
|
|
font-weight: 700;
|
|
padding: 1px 6px;
|
|
border-radius: 4px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.25);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="legend">
|
|
WS: <span id="ws_state">connecting</span><br>
|
|
Last: <span id="last_msg">—</span>
|
|
</div>
|
|
<div id="map"></div>
|
|
|
|
<script>
|
|
// --- Map init (center near your sample points; adjust as desired)
|
|
const map = L.map('map').setView([44.93655, -123.02185], 17);
|
|
|
|
// Basemap: replace with your own tile server if you have one
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
maxZoom: 20,
|
|
attribution: '© OpenStreetMap contributors'
|
|
}).addTo(map);
|
|
|
|
// Node colors (bright, readable)
|
|
const nodeStyle = {
|
|
B: { color: '#00e5ff' }, // cyan-ish
|
|
C: { color: '#ffea00' }, // yellow
|
|
D: { color: '#ff3d00' }, // orange/red
|
|
E: { color: '#76ff03' }, // green
|
|
};
|
|
// One persistent "current" marker per node (bright + labeled)
|
|
const currentMarkers = {}; // node -> L.CircleMarker
|
|
|
|
function addFadingDot(node, lat, lon) {
|
|
const style = nodeStyle[node] || { color: '#ffffff' };
|
|
|
|
// Bright at birth
|
|
const m = L.circleMarker([lat, lon], {
|
|
radius: 7,
|
|
color: style.color,
|
|
weight: 2,
|
|
fillColor: style.color,
|
|
fillOpacity: 1.0,
|
|
opacity: 1.0
|
|
}).addTo(map);
|
|
|
|
// Fade steps: 5s -> 2/3, 10s -> 1/3, 15s -> remove
|
|
setTimeout(() => {
|
|
m.setStyle({ fillOpacity: 0.66, opacity: 0.66 });
|
|
}, 5000);
|
|
|
|
setTimeout(() => {
|
|
m.setStyle({ fillOpacity: 0.33, opacity: 0.33 });
|
|
}, 10000);
|
|
|
|
setTimeout(() => {
|
|
map.removeLayer(m);
|
|
}, 15000);
|
|
}
|
|
|
|
function upsertCurrentMarker(node, lat, lon) {
|
|
const style = nodeStyle[node] || { color: '#ffffff' };
|
|
|
|
if (!currentMarkers[node]) {
|
|
const m = L.circleMarker([lat, lon], {
|
|
radius: 9,
|
|
color: style.color,
|
|
weight: 2,
|
|
fillColor: style.color,
|
|
fillOpacity: 1.0,
|
|
opacity: 1.0
|
|
}).addTo(map);
|
|
|
|
// Permanent label next to the current marker
|
|
m.bindTooltip(node, {
|
|
permanent: true,
|
|
direction: 'right',
|
|
offset: [10, 0],
|
|
className: 'node-label'
|
|
});
|
|
|
|
currentMarkers[node] = m;
|
|
} else {
|
|
currentMarkers[node].setLatLng([lat, lon]);
|
|
// reassert brightness in case you later add effects
|
|
currentMarkers[node].setStyle({ fillOpacity: 1.0, opacity: 1.0 });
|
|
}
|
|
}
|
|
function parseMsg(line) {
|
|
// "C,44.936454,-123.021923,65.40"
|
|
const parts = line.trim().split(',');
|
|
if (parts.length < 4) return null;
|
|
const node = parts[0];
|
|
const lat = parseFloat(parts[1]);
|
|
const lon = parseFloat(parts[2]);
|
|
const alt = parseFloat(parts[3]); // not used yet, but keep it
|
|
if (!node || Number.isNaN(lat) || Number.isNaN(lon)) return null;
|
|
return { node, lat, lon, alt };
|
|
}
|
|
|
|
// --- WebSocket
|
|
const wsStateEl = document.getElementById('ws_state');
|
|
const lastMsgEl = document.getElementById('last_msg');
|
|
|
|
//const wsUrl = `ws://${location.hostname}:3000/stream`;
|
|
//const ws = new WebSocket(wsUrl);
|
|
|
|
const wsScheme = (location.protocol === 'https:') ? 'wss' : 'ws';
|
|
const wsUrl = (wsScheme === 'ws')
|
|
? `ws://${location.hostname}:3000/stream`
|
|
: `wss://${location.host}/stream`;
|
|
|
|
const ws = new WebSocket(wsUrl);
|
|
//const ws = new WebSocket('ws://localhost:3000/stream'); // temporary test
|
|
|
|
ws.onopen = () => wsStateEl.textContent = 'open';
|
|
ws.onclose = () => wsStateEl.textContent = 'closed';
|
|
ws.onerror = () => wsStateEl.textContent = 'error';
|
|
|
|
ws.onmessage = (ev) => {
|
|
const line = ev.data;
|
|
lastMsgEl.textContent = line;
|
|
|
|
const msg = parseMsg(line);
|
|
if (!msg) return;
|
|
|
|
addFadingDot(msg.node, msg.lat, msg.lon);
|
|
upsertCurrentMarker(msg.node, msg.lat, msg.lon);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>
|