- 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.
60 lines
1.7 KiB
JavaScript
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)
|
|
}
|
|
}
|
|
}
|