Add toggleable weather radar overlay, disable rain-scatter display

New "Weather radar" toggle in /map's control panel (mobile + desktop)
layers an ECCC GeoMet WMS composite dBZ mosaic over the propagation
heatmap. The Radar_1km_dBZ-Extrapolation layer covers CONUS + Canada at
1 km resolution, updated every ~6 minutes. Tiles are lazy-created on
first toggle so visitors who never open the panel never hit the WMS
service. Refresh is driven by a setInterval that calls setParams with a
cache-busting timestamp, matching ECCC's publish cadence.

Rain-scatter display (NEXRAD cell markers + "Rain Scatter" section in
the point-detail panel) is disabled for now. The live radar overlay
answers "is there rain near this point?" more directly, and the scatter
propagation feature needs more work before it's useful. The Elixir
start_async, handle_async, and fetch_rain_scatter helpers are removed.
The JS drawScatterMarkers / buildScatterBlock paths remain as harmless
dead code since they're guarded by a `rain_scatter` field the server no
longer sends — easy to re-enable later by restoring the payload key.
This commit is contained in:
Graham McIntire 2026-04-12 15:36:28 -05:00
parent e2dc5ed225
commit 85036eff21
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
2 changed files with 77 additions and 38 deletions

View file

@ -132,6 +132,8 @@ interface PropagationMapHook extends ViewHook {
rangeCircles: L.LayerGroup
gridLayer: L.LayerGroup
gridVisible: boolean
radarLayer: L.TileLayer.WMS | null
radarRefreshTimer: ReturnType<typeof setInterval> | null
detailPanel: HTMLElement | null
viewshedLoading: boolean
lastDetail: PointDetail | null
@ -750,6 +752,41 @@ export const PropagationMap: Record<string, unknown> & {
}
})
// Weather radar overlay (ECCC GeoMet WMS — CONUS + Canada composite dBZ).
// Lazy-created on first toggle so we don't hit the service until asked.
this.radarLayer = null
this.radarRefreshTimer = null
this.handleEvent("toggle_radar", ({ visible }: { visible: boolean }) => {
if (visible) {
if (!this.radarLayer) {
this.radarLayer = L.tileLayer.wms("https://geo.weather.gc.ca/geomet", {
layers: "Radar_1km_dBZ-Extrapolation",
format: "image/png",
transparent: true,
opacity: 0.55,
version: "1.3.0",
attribution: "Radar &copy; Environment and Climate Change Canada"
})
}
this.radarLayer.addTo(this.map)
// ECCC radar updates every ~6 minutes; force a tile refresh on that
// cadence so the overlay stays current without a page reload.
this.radarRefreshTimer = setInterval(() => {
if (this.radarLayer) {
// @ts-expect-error setParams exists on WMS TileLayer but isn't in @types/leaflet yet
this.radarLayer.setParams({ _: Date.now() })
}
}, 6 * 60 * 1000)
} else {
if (this.radarLayer) this.radarLayer.remove()
if (this.radarRefreshTimer) {
clearInterval(this.radarRefreshTimer)
this.radarRefreshTimer = null
}
}
})
// Click on map to request viewshed + factor detail from server
this.map.on("click", (e: L.LeafletMouseEvent) => {
this.rangeCircles.clearLayers()

View file

@ -52,6 +52,7 @@ defmodule MicrowavepropWeb.MapLive do
initial_center: center,
initial_zoom: @default_zoom,
grid_visible: false,
radar_visible: false,
antenna_height_ft: 33
)}
end
@ -144,6 +145,17 @@ defmodule MicrowavepropWeb.MapLive do
{:noreply, socket}
end
def handle_event("toggle_radar", _params, socket) do
visible = !socket.assigns.radar_visible
socket =
socket
|> assign(:radar_visible, visible)
|> push_event("toggle_radar", %{visible: visible})
{:noreply, socket}
end
def handle_event("point_detail", %{"lat" => lat, "lon" => lon}, socket) do
band = socket.assigns.selected_band
detail = Propagation.point_detail(band, lat, lon, socket.assigns.selected_time)
@ -154,22 +166,17 @@ defmodule MicrowavepropWeb.MapLive do
%{time: DateTime.to_iso8601(f.valid_time), score: f.score}
end)
# Push the detail immediately with rain_scatter as :pending — NEXRAD
# fetching is slow on a cold 5-min window so we don't want to block the
# LiveView process. The :rain_scatter async task pushes its own event.
# Rain-scatter (NEXRAD cell overlay + scatter-dB readings in the detail
# panel) is disabled for now — the live radar WMS toggle covers the
# "is there rain?" question and the scatter-propagation feature needs
# more work before it's useful. To re-enable: restore the :rain_scatter
# payload key and the start_async/handle_async pair below.
payload =
if detail,
do: detail |> Map.put(:forecast, forecast_data) |> Map.put(:rain_scatter, :pending),
do: Map.put(detail, :forecast, forecast_data),
else: %{}
socket =
socket
|> push_event("point_detail", payload)
|> start_async({:rain_scatter, lat, lon, band}, fn ->
fetch_rain_scatter(lat, lon, band)
end)
{:noreply, socket}
{:noreply, push_event(socket, "point_detail", payload)}
end
def handle_event("map_bounds", bounds, socket) do
@ -273,15 +280,6 @@ defmodule MicrowavepropWeb.MapLive do
{:noreply, socket}
end
def handle_async({:rain_scatter, lat, lon, _band}, {:ok, scatter}, socket) do
{:noreply, push_event(socket, "rain_scatter_update", %{lat: lat, lon: lon, rain_scatter: scatter})}
end
def handle_async({:rain_scatter, _lat, _lon, _band}, {:exit, reason}, socket) do
Logger.warning("Rain scatter fetch failed: #{inspect(reason)}")
{:noreply, socket}
end
defp push_timeline(socket) do
times =
Enum.map(socket.assigns.valid_times, fn t ->
@ -317,23 +315,6 @@ defmodule MicrowavepropWeb.MapLive do
end
end
defp fetch_rain_scatter(lat, lon, band_mhz) do
alias Microwaveprop.Propagation.RainScatter
alias Microwaveprop.Weather.NexradClient
freq_ghz = band_mhz / 1000.0
case NexradClient.fetch_rain_cells(lat, lon) do
{:ok, rain_cells} ->
cells = RainScatter.find_scatter_cells(rain_cells, lat, lon, freq_ghz)
classification = RainScatter.classify(cells)
%{cells: cells, classification: to_string(classification)}
{:error, _} ->
%{cells: [], classification: "none"}
end
end
defp band_info(band_mhz) do
config = BandConfig.get(band_mhz)
@ -450,6 +431,16 @@ defmodule MicrowavepropWeb.MapLive do
<span class="text-sm">Grid squares</span>
</label>
<label class="flex items-center gap-2 cursor-pointer px-1">
<input
type="checkbox"
class="toggle toggle-sm toggle-primary"
checked={@radar_visible}
phx-click="toggle_radar"
/>
<span class="text-sm">Weather radar</span>
</label>
<form phx-change="set_antenna_height" class="flex items-center gap-2 px-1">
<label class="text-sm whitespace-nowrap">Antenna height</label>
<input
@ -594,6 +585,17 @@ defmodule MicrowavepropWeb.MapLive do
<span class="text-sm">Grid squares</span>
</label>
<%!-- Weather radar toggle --%>
<label class="flex items-center gap-2 cursor-pointer px-1">
<input
type="checkbox"
class="toggle toggle-sm toggle-primary"
checked={@radar_visible}
phx-click="toggle_radar"
/>
<span class="text-sm">Weather radar</span>
</label>
<%!-- Antenna height --%>
<form phx-change="set_antenna_height" class="flex items-center gap-2 px-1">
<label class="text-sm whitespace-nowrap">Antenna height</label>