Files
trx-rs/docs/Wxsat-Map-Overlay.md
T
Claude 560b6ec912 [feat](trx-rs): add weather satellite map overlay integration
Add SGP4-based geo-referencing for NOAA APT and Meteor LRPT decoded
satellite images, enabling them to be displayed as semi-transparent
overlays on the Leaflet map module with ground track polylines.

Changes:
- Add sgp4 crate dependency to trx-core for orbital propagation
- New trx-core/src/geo.rs module with TLE-based pass geo-referencing,
  ECI-to-geodetic conversion, and station-location fallback estimation
- Extend WxsatImage and LrptImage structs with geo_bounds and
  ground_track optional fields (backward compatible via serde defaults)
- Compute geo-bounds in finalize_wxsat_pass and finalize_lrpt_pass
  using satellite identity, pass timestamps, and station coordinates
- Add 'wxsat' source filter to the map module (off by default)
- Add L.imageOverlay rendering with popup and ground track polyline
- Add "Show on Map" buttons in wxsat plugin live/history views

https://claude.ai/code/session_01DUCfb9CjGoViwBrznpfWyt
Signed-off-by: Claude <noreply@anthropic.com>
2026-03-28 12:24:36 +01:00

5.7 KiB

Weather Satellite Map Overlay Integration

Overlay decoded NOAA APT and Meteor-M LRPT satellite images on the Leaflet map module, with ground track visualisation and source filtering.

Created: 2026-03-28

Status

Step Description Status
1 Add sgp4 crate, create trx-core/src/geo.rs Done
2 Extend WxsatImage/LrptImage with geo fields Done
3 Compute geo-bounds in finalize_wxsat_pass / finalize_lrpt_pass Done
4 Add wxsat to map source filter + image overlay rendering Done
5 Add ground track polyline + filter toggle UI Done
6 Build, test, verify Done

Motivation

The wxsat plugin currently shows a history table with download links but has no geographic context. Since the Map module already renders APRS, AIS, VDES, and FTx/WSPR positions, weather satellite images are a natural addition — they can be projected as semi-transparent overlays on the same Leaflet map.

Architecture

Data flow

Pass decoded (APT / LRPT)
  ↓ finalize_wxsat_pass / finalize_lrpt_pass  (trx-server/audio.rs)
  ↓ SGP4 propagation using satellite TLE + pass timestamps
  ↓ Compute geo_bounds [[south, west], [north, east]]
  ↓ Compute ground_track [[lat, lon], ...]
  ↓ Attach to WxsatImage / LrptImage
  ↓ Broadcast via DecodedMessage
  ↓ SSE → browser
  ↓ wxsat.js: L.imageOverlay() + L.polyline() on aprsMap

Geo-referencing strategy

Weather satellites (NOAA POES, Meteor-M) fly sun-synchronous polar orbits at ~850 km altitude with known TLE parameters. Given:

  • Satellite identity (from telemetry: NOAA-15/18/19, Meteor-M N2-3/N2-4)
  • Pass start/end timestamps (pass_start_ms, pass_end_ms)
  • Receiver station lat/lon (from RigState.server_latitude/longitude)

We can use SGP4 propagation (via the sgp4 crate) to compute the sub-satellite ground track during the pass, then derive image bounds from the known swath geometry:

Parameter NOAA APT Meteor LRPT
Altitude ~850 km ~825 km
Swath width ~2800 km ~2800 km
Ground speed ~6.9 km/s ~6.9 km/s
Scan rate 2 lines/sec (0.5s/line) variable MCU rate
Image width 909 px/channel 1568 px

Bounds computation:

  1. Propagate satellite position at pass_start_ms and pass_end_ms
  2. Sub-satellite points define the ground track center line
  3. Swath half-width (~1400 km) gives east/west extent
  4. Image is projected as a simple lat/lon rectangle (acceptable distortion for the typical ~15° latitude span of a single pass)

TLE source: Hardcoded recent TLEs for the 5 active satellites, with an optional HTTP refresh from CelesTrak. Stale TLEs (weeks old) still give sub-degree accuracy for image overlay purposes.

Crate changes

trx-core (src/trx-core/)

New module src/trx-core/src/geo.rs:

  • SatelliteGeo struct: holds hardcoded TLEs, provides compute_pass_bounds()
  • PassGeoBounds { south: f64, west: f64, north: f64, east: f64 }
  • ground_track(sat, start_ms, end_ms) -> Vec<[f64; 2]>
  • Uses sgp4 crate for orbital propagation
  • Falls back to station-centered approximation when TLE unavailable

src/trx-core/src/decode.rs — extend structs:

pub struct WxsatImage {
    // ... existing fields ...
    pub geo_bounds: Option<[f64; 4]>,     // [south, west, north, east]
    pub ground_track: Option<Vec<[f64; 2]>>, // [[lat, lon], ...]
}
// Same for LrptImage

trx-server (src/trx-server/)

src/trx-server/src/audio.rs:

  • In finalize_wxsat_pass: after PNG write, call SatelliteGeo::compute_pass_bounds() using satellite name, pass timestamps, and station lat/lon (threaded through from config). Attach result to WxsatImage.
  • Same for finalize_lrpt_pass.

Frontend (trx-frontend-http/assets/web/)

plugins/wxsat.js:

  • On onServerWxsatImage / onServerLrptImage: if geo_bounds present, call window.addWxsatMapOverlay(msg).
  • Manage overlay list, allow removal.

app.js:

  • Add wxsat: false to DEFAULT_MAP_SOURCE_FILTER (off by default to avoid visual clutter; users opt-in).
  • window.addWxsatMapOverlay(msg): creates L.imageOverlay(msg.path, bounds) with opacity 0.6, adds to mapMarkers set with __trxType = "wxsat".
  • window.addWxsatGroundTrack(msg): creates L.polyline(msg.ground_track) with dashed style.
  • Overlay list in wxsat panel with per-image show/hide toggle.

index.html:

  • No structural changes needed; the map filter chip system auto-generates from DEFAULT_MAP_SOURCE_FILTER.

style.css:

  • Styling for wxsat overlay opacity slider (future enhancement).

Dependencies

Crate Version Purpose
sgp4 2.4 Pure Rust SGP4 orbital propagation

Added to trx-core/Cargo.toml (used by geo.rs).

Risk / Limitations

  • Rectangular projection approximation: The actual scan geometry is curved (satellite moves along a great circle), but for a single pass spanning ~15-20° of latitude, a lat/lon rectangle is a reasonable first approximation. More accurate warping could use L.imageOverlay with a canvas transform in a future iteration.

  • TLE staleness: Hardcoded TLEs drift ~0.1°/week. For overlay purposes this is acceptable. A periodic CelesTrak fetch would keep them fresh.

  • Image rotation: Ascending vs descending passes produce different orientations. The initial implementation uses axis-aligned bounds (no rotation). A rotated overlay would need leaflet-imageoverlay-rotated or a canvas-based approach — deferred to a follow-up.

  • Image serving: The path field is a filesystem path. On co-located server/client setups this works directly. Remote setups may need an image-serving endpoint (out of scope for this change).