Replace the idealised concentric-circle range rings with a realistic per-HRRR-grid-cell reception map. For every 0.125° grid point within the band's exceptional range, the estimator computes great-circle distance, FSPL + atmospheric loss, and applies a cell-specific score adjustment of (50 - score) * 0.3 dB — score 100 gives a 15 dB ducting boost, score 0 a 15 dB penalty. Cells below the -145 dBm detection floor are dropped. The beacon map hook now renders each surviving cell as a 0.125° filled rectangle with a tooltip showing tier, Rx dBm, distance, and HRRR score, so the coverage footprint bulges where ducting conditions are good and cuts off where they aren't, instead of being a perfect circle. Falls back to score 50 for cells that don't have HRRR data yet, so the plot is always useful. Also fixes the prior all-grey-tiles regression by restoring an explicit setView() at map creation and adding a post-mount invalidateSize() for when the map is placed inside a flex container.
66 lines
2 KiB
JavaScript
66 lines
2 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 cells = JSON.parse(this.el.dataset.cells || "[]")
|
|
const step = parseFloat(this.el.dataset.gridStep || "0.125")
|
|
|
|
const map = L.map(this.el, {
|
|
zoomControl: true,
|
|
attributionControl: false,
|
|
scrollWheelZoom: false
|
|
}).setView([lat, lon], 9)
|
|
|
|
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
maxZoom: 19
|
|
}).addTo(map)
|
|
|
|
// Draw each HRRR grid cell as a filled rectangle colored by its received
|
|
// signal tier. Use a single layerGroup so zoom/pan stays fast.
|
|
const cellLayer = L.layerGroup().addTo(map)
|
|
const half = step / 2.0
|
|
|
|
const allBounds = []
|
|
for (const c of cells) {
|
|
const sw = [c.lat - half, c.lon - half]
|
|
const ne = [c.lat + half, c.lon + half]
|
|
const rect = L.rectangle([sw, ne], {
|
|
color: c.color,
|
|
weight: 0,
|
|
fillColor: c.color,
|
|
fillOpacity: 0.55,
|
|
interactive: true
|
|
})
|
|
rect.bindTooltip(
|
|
`${c.label}<br>${c.rx_dbm} dBm<br>${c.distance_km} km · score ${c.score}`,
|
|
{sticky: true}
|
|
)
|
|
rect.addTo(cellLayer)
|
|
allBounds.push(sw, ne)
|
|
}
|
|
|
|
// Beacon marker on top.
|
|
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 rendered cells (initial view already set above).
|
|
if (allBounds.length > 0) {
|
|
map.fitBounds(L.latLngBounds(allBounds), {padding: [20, 20]})
|
|
}
|
|
|
|
setTimeout(() => map.invalidateSize(), 50)
|
|
}
|
|
}
|