feat: Weather satellite and ADS-B trail rendering improvements

Weather Satellite:
- Fix duplicate event listeners on mode re-entry via locationListenersAttached guard
- Add suspend() to stop countdown/SSE stream when switching away from the mode
- Call WeatherSat.suspend() in switchMode() when leaving weathersat
- Fix toggleScheduler() to take the checkbox element as source of truth,
  preventing both checkboxes fighting each other
- Reset isRunning/UI state after auto-capture completes (scheduler path)
- Always re-select first pass and reset selectedPassIndex after loadPasses()
- Keep timeline cursor in sync inside selectPass()
- Add seconds to pass ID format to avoid collisions on concurrent passes
- Improve predict_passes() comment clarity; fix trajectory comment

ADS-B dashboard:
- Batch altitude-colour trail segments into runs of same-colour polylines,
  reducing Leaflet layer count from O(trail length) to O(colour changes)
  for significantly better rendering performance with many aircraft

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-02-18 11:43:47 +00:00
parent b0652595fa
commit f4ade209f9
5 changed files with 104 additions and 38 deletions

View File

@@ -1345,21 +1345,41 @@ ACARS: ${r.statistics.acarsMessages} messages`;
}
trailLines[icao] = [];
// Create gradient segments
// Group consecutive same-altitude-color points into single polylines.
// This reduces layer count from O(trail length) to O(color band changes),
// which is typically 1-2 polylines per aircraft instead of up to 99.
const now = Date.now();
for (let i = 1; i < trail.length; i++) {
const p1 = trail[i-1];
const p2 = trail[i];
const age = (now - p2.time) / 1000; // seconds
const opacity = Math.max(0.2, 1 - (age / 120)); // Fade over 2 minutes
let runColor = getAltitudeColor(trail[0].alt);
let runPoints = [[trail[0].lat, trail[0].lon]];
let runEndTime = trail[0].time;
const color = getAltitudeColor(p2.alt);
const line = L.polyline([[p1.lat, p1.lon], [p2.lat, p2.lon]], {
color: color,
weight: 2,
opacity: opacity
}).addTo(radarMap);
trailLines[icao].push(line);
for (let i = 1; i < trail.length; i++) {
const p = trail[i];
const color = getAltitudeColor(p.alt);
if (color !== runColor) {
// Flush the current color run as one polyline
if (runPoints.length >= 2) {
const opacity = Math.max(0.2, 1 - ((now - runEndTime) / 1000 / 120));
trailLines[icao].push(
L.polyline(runPoints, { color: runColor, weight: 2, opacity }).addTo(radarMap)
);
}
// Start a new run, sharing the junction point for visual continuity
runColor = color;
runPoints = [[trail[i-1].lat, trail[i-1].lon], [p.lat, p.lon]];
} else {
runPoints.push([p.lat, p.lon]);
}
runEndTime = p.time;
}
// Flush the final run
if (runPoints.length >= 2) {
const opacity = Math.max(0.2, 1 - ((now - runEndTime) / 1000 / 120));
trailLines[icao].push(
L.polyline(runPoints, { color: runColor, weight: 2, opacity }).addTo(radarMap)
);
}
}

View File

@@ -2696,7 +2696,7 @@
<div class="wxsat-strip-divider"></div>
<div class="wxsat-strip-group">
<label class="wxsat-schedule-toggle" title="Auto-capture passes">
<input type="checkbox" id="wxsatAutoSchedule" onchange="WeatherSat.toggleScheduler()">
<input type="checkbox" id="wxsatAutoSchedule" onchange="WeatherSat.toggleScheduler(this)">
<span class="wxsat-toggle-label">AUTO</span>
</label>
</div>
@@ -4036,6 +4036,11 @@
if (typeof SpaceWeather !== 'undefined' && SpaceWeather.destroy) SpaceWeather.destroy();
}
// Suspend Weather Satellite background timers/streams when leaving the mode
if (mode !== 'weathersat') {
if (typeof WeatherSat !== 'undefined' && WeatherSat.suspend) WeatherSat.suspend();
}
// Show/hide Device Intelligence for modes that use it (not for satellite/aircraft/tscm)
const reconBtn = document.getElementById('reconBtn');
const intelBtn = document.querySelector('[onclick="exportDeviceDB()"]');

View File

@@ -1,15 +1,15 @@
<!-- WEATHER SATELLITE MODE -->
<div id="weatherSatMode" class="mode-content">
<div class="section">
<h3>Weather Satellite Decoder</h3>
<div class="alpha-mode-notice">
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
</div>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
Receive and decode weather images from NOAA and Meteor satellites.
Uses SatDump for live SDR capture and image processing.
</p>
</div>
<div id="weatherSatMode" class="mode-content">
<div class="section">
<h3>Weather Satellite Decoder</h3>
<div class="alpha-mode-notice">
ALPHA: Weather Satellite capture is experimental and may fail depending on SatDump version, SDR driver support, and pass conditions.
</div>
<p class="info-text" style="font-size: 11px; color: var(--text-dim); margin-bottom: 12px;">
Receive and decode weather images from NOAA and Meteor satellites.
Uses SatDump for live SDR capture and image processing.
</p>
</div>
<div class="section">
<h3>Satellite</h3>
@@ -226,7 +226,7 @@
</p>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 6px;">
<input type="checkbox" id="wxsatSidebarAutoSchedule" onchange="WeatherSat.toggleScheduler()" style="width: auto;">
<input type="checkbox" id="wxsatSidebarAutoSchedule" onchange="WeatherSat.toggleScheduler(this)" style="width: auto;">
Enable Auto-Capture
</label>
</div>