From 1796ee4cb75a5a8c894a31350a8ac50b6d01741b Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Fri, 3 Apr 2026 15:11:55 -0500 Subject: [PATCH] Add WeatherMapLive LiveView with layer pill buttons --- .../live/weather_map_live.ex | 165 ++++++++++++++++++ lib/microwaveprop_web/router.ex | 1 + 2 files changed, 166 insertions(+) create mode 100644 lib/microwaveprop_web/live/weather_map_live.ex diff --git a/lib/microwaveprop_web/live/weather_map_live.ex b/lib/microwaveprop_web/live/weather_map_live.ex new file mode 100644 index 00000000..11fc7d66 --- /dev/null +++ b/lib/microwaveprop_web/live/weather_map_live.ex @@ -0,0 +1,165 @@ +defmodule MicrowavepropWeb.WeatherMapLive do + @moduledoc false + use MicrowavepropWeb, :live_view + + alias Microwaveprop.Weather + + @initial_bounds %{ + "south" => 29.5, + "north" => 36.3, + "west" => -101.5, + "east" => -92.5 + } + + @layers [ + %{id: "refractivity_gradient", label: "Refractivity Gradient", unit: "N/km"}, + %{id: "bl_height", label: "Boundary Layer", unit: "m"}, + %{id: "dewpoint_depression", label: "Dewpoint Depression", unit: "°C"}, + %{id: "pwat", label: "Precipitable Water", unit: "mm"}, + %{id: "temperature", label: "Temperature", unit: "°C"}, + %{id: "ducting", label: "Ducting Detected", unit: ""} + ] + + @impl true + def mount(_params, _session, socket) do + if connected?(socket) do + Phoenix.PubSub.subscribe(Microwaveprop.PubSub, "weather:updated") + end + + data = Weather.latest_weather_grid(@initial_bounds) + valid_time = if data != [], do: hd(data).valid_time + + {:ok, + assign(socket, + page_title: "Weather Map", + layers: @layers, + selected_layer: "refractivity_gradient", + initial_data_json: Jason.encode!(data), + valid_time: valid_time, + bounds: @initial_bounds + )} + end + + @impl true + def handle_event("select_layer", %{"layer" => layer_id}, socket) do + {:noreply, assign(socket, :selected_layer, layer_id)} + end + + def handle_event("map_bounds", bounds, socket) do + data = Weather.latest_weather_grid(bounds) + + socket = + socket + |> assign(:bounds, bounds) + |> push_event("update_weather", %{data: data}) + + {:noreply, socket} + end + + def handle_event("point_detail", %{"lat" => lat, "lon" => lon}, socket) do + detail = + if socket.assigns.valid_time do + Weather.weather_point_detail(lat, lon, socket.assigns.valid_time) + end + + payload = detail || %{} + {:noreply, push_event(socket, "point_detail", payload)} + end + + @impl true + def handle_info({:weather_updated, _valid_time}, socket) do + data = Weather.latest_weather_grid(socket.assigns.bounds) + valid_time = if data != [], do: hd(data).valid_time + + socket = + socket + |> assign(:valid_time, valid_time) + |> push_event("update_weather", %{data: data}) + + {:noreply, socket} + end + + @impl true + def render(assigns) do + ~H""" +
+ <%!-- Full-page map --%> +
+
+ + <%!-- Top-left control panel --%> +
+
+
+
+ + Weather +
+ <%= if @valid_time do %> + {Calendar.strftime(@valid_time, "%Y-%m-%d %H:%M UTC")} + <% else %> + No data available + <% end %> +
+
+
+
+
+ + <%!-- Layer pill buttons --%> +
+ +
+ + <%!-- Links --%> +
+ <.link navigate="/map" class="btn btn-xs btn-ghost justify-start gap-1.5"> + <.icon name="hero-signal" class="size-3.5" /> Propagation Map + +
+
+
+ + <%!-- Point detail panel --%> + +
+ + + """ + end +end diff --git a/lib/microwaveprop_web/router.ex b/lib/microwaveprop_web/router.ex index 6bd76b4a..c72cc179 100644 --- a/lib/microwaveprop_web/router.ex +++ b/lib/microwaveprop_web/router.ex @@ -24,6 +24,7 @@ defmodule MicrowavepropWeb.Router do live "/submit", SubmitLive live "/map", MapLive + live "/weather", WeatherMapLive live "/contacts", ContactLive.Index live "/contacts/map", ContactMapLive live "/contacts/:id", ContactLive.Show