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:
parent
e2dc5ed225
commit
85036eff21
2 changed files with 77 additions and 38 deletions
|
|
@ -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 © 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()
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue