prop/assets/js/beacon_map_hook.js
Graham McIntire a5b3f1f3da Beacon detail map with range estimate and on_the_air flag
- Add on_the_air boolean to beacons (default true); surfaced as a
  checkbox on the form, a badge on the index, and in the detail list.
- Render a Leaflet map on the beacon show page with a marker at the
  beacon's lat/lon tooltipped with callsign + frequency. Marker is
  green when on air, gray when off.
- Compute and draw a reception-range estimate as concentric signal-
  strength rings. New Microwaveprop.Beacons.RangeEstimate solves a
  link budget (FSPL + O2/H2O absorption from BandConfig) at five RX
  thresholds (-100 to -145 dBm), then scales by 0.5 + score/100 from
  the latest Propagation.point_detail at the beacon's grid square, so
  current HRRR conditions shift the rings in or out.
- Re-enable the hourly PropagationGridWorker cron and freshness
  monitor in dev.exs so dev actually has HRRR-backed scores to feed
  the new estimator.
2026-04-08 13:29:58 -05:00

60 lines
1.7 KiB
JavaScript

export const BeaconMap = {
mounted() {
const lat = parseFloat(this.el.dataset.lat)
const lon = parseFloat(this.el.dataset.lon)
const label = this.el.dataset.label || ""
const onTheAir = this.el.dataset.onTheAir === "true"
const rings = JSON.parse(this.el.dataset.rings || "[]")
const map = L.map(this.el, {
zoomControl: true,
attributionControl: false,
scrollWheelZoom: false
})
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19
}).addTo(map)
// Draw rings outermost-first so stronger tiers stack on top.
const sorted = [...rings].sort((a, b) => b.radius_km - a.radius_km)
const drawn = []
for (const ring of sorted) {
if (!ring.radius_km || ring.radius_km <= 0) continue
const circle = L.circle([lat, lon], {
radius: ring.radius_km * 1000,
color: ring.color,
weight: 1.5,
opacity: 0.9,
fillColor: ring.color,
fillOpacity: 0.12
}).addTo(map)
circle.bindTooltip(`${ring.label}: ${ring.rx_dbm} dBm · ${ring.radius_km} km`, {
sticky: true
})
drawn.push(circle)
}
const markerColor = onTheAir ? "#16a34a" : "#94a3b8"
const markerFill = onTheAir ? "#22c55e" : "#cbd5e1"
L.circleMarker([lat, lon], {
radius: 7,
color: markerColor,
fillColor: markerFill,
fillOpacity: 1.0,
weight: 2
}).addTo(map).bindTooltip(label, {
permanent: true,
direction: "top",
offset: [0, -10]
})
// Fit to the largest ring, or fall back to a reasonable zoom around the point.
if (drawn.length > 0) {
map.fitBounds(drawn[0].getBounds(), {padding: [20, 20]})
} else {
map.setView([lat, lon], 9)
}
}
}