165 lines
5 KiB
Elixir
165 lines
5 KiB
Elixir
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"""
|
|
<div class="relative w-screen h-screen overflow-hidden">
|
|
<%!-- Full-page map --%>
|
|
<div
|
|
id="weather-map"
|
|
phx-hook="WeatherMap"
|
|
phx-update="ignore"
|
|
data-weather={@initial_data_json}
|
|
data-layers={Jason.encode!(@layers)}
|
|
data-selected-layer={@selected_layer}
|
|
class="absolute inset-0 z-0"
|
|
>
|
|
</div>
|
|
|
|
<%!-- Top-left control panel --%>
|
|
<div
|
|
id="weather-controls"
|
|
class="absolute top-2 left-12 z-[1000] flex flex-col gap-2 max-w-[calc(100vw-4rem)] md:left-14 md:top-3 md:max-w-none"
|
|
>
|
|
<div class="bg-base-100/90 shadow rounded-box border border-base-300 p-2 md:p-3 flex flex-col gap-2">
|
|
<div class="font-bold text-sm leading-tight px-1 flex items-center justify-between gap-2">
|
|
<div class="min-w-0">
|
|
<span class="hidden md:inline">HRRR Weather Data</span>
|
|
<span class="md:hidden">Weather</span>
|
|
<div class="font-normal text-xs opacity-70">
|
|
<%= if @valid_time do %>
|
|
{Calendar.strftime(@valid_time, "%Y-%m-%d %H:%M UTC")}
|
|
<% else %>
|
|
No data available
|
|
<% end %>
|
|
</div>
|
|
</div>
|
|
<div
|
|
id="weather-utc-clock"
|
|
phx-hook="UtcClock"
|
|
class="font-mono text-xs opacity-70 tabular-nums shrink-0"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<%!-- Layer pill buttons --%>
|
|
<div class="flex flex-wrap gap-1">
|
|
<button
|
|
:for={layer <- @layers}
|
|
phx-click="select_layer"
|
|
phx-value-layer={layer.id}
|
|
class={[
|
|
"btn btn-xs rounded-full",
|
|
if(@selected_layer == layer.id, do: "btn-primary", else: "btn-ghost")
|
|
]}
|
|
>
|
|
{layer.label}
|
|
</button>
|
|
</div>
|
|
|
|
<%!-- Links --%>
|
|
<div class="flex flex-col gap-1 border-t border-base-300 pt-2">
|
|
<.link navigate="/map" class="btn btn-xs btn-ghost justify-start gap-1.5">
|
|
<.icon name="hero-signal" class="size-3.5" /> Propagation Map
|
|
</.link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<%!-- Point detail panel --%>
|
|
<div
|
|
id="weather-detail-panel"
|
|
phx-update="ignore"
|
|
class={[
|
|
"bg-neutral text-neutral-content shadow-lg border border-base-300 overflow-hidden overflow-y-auto z-[1001]",
|
|
"fixed bottom-0 left-0 right-0 max-h-[50vh] rounded-t-2xl",
|
|
"md:absolute md:top-3 md:bottom-auto md:left-auto md:right-auto md:max-h-[60vh] md:max-w-sm md:rounded-box"
|
|
]}
|
|
style="display:none;"
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<Layouts.flash_group flash={@flash} />
|
|
"""
|
|
end
|
|
end
|