From 383c18e50a15bbe16f65d4c109be122d8fa2f8ed Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Sat, 4 Apr 2026 11:35:30 -0500 Subject: [PATCH] Show IEMRE gridded reanalysis data on contact detail page --- .../live/contact_live/show.ex | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex index 14676741..7affd5c7 100644 --- a/lib/microwaveprop_web/live/contact_live/show.ex +++ b/lib/microwaveprop_web/live/contact_live/show.ex @@ -38,6 +38,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do terrain = Terrain.get_terrain_profile(contact.id) contact = maybe_enqueue_terrain(terrain, contact) contact = maybe_enqueue_weather(weather, contact) + iemre = load_iemre(contact) elevation_profile = compute_elevation_profile(contact, hrrr_path, weather.soundings) propagation_analysis = build_propagation_analysis(contact, hrrr, terrain, elevation_profile, weather.soundings) data_sources = build_data_sources(hrrr, hrrr_path, terrain, weather, elevation_profile) @@ -50,6 +51,7 @@ defmodule MicrowavepropWeb.ContactLive.Show do soundings: weather.soundings, solar: solar, hrrr: hrrr, + iemre: iemre, terrain: terrain, elevation_profile: elevation_profile, propagation_analysis: propagation_analysis, @@ -254,6 +256,18 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp sounding_sort_key("lifted_index"), do: &(&1.lifted_index || 0) defp sounding_sort_key(_), do: fn s -> s.station.name || s.station.station_code end + defp load_iemre(contact) do + case Weather.iemre_for_contact(contact) do + %{hourly: hourly} = obs when is_list(hourly) and hourly != [] -> + qso_hour = contact.qso_timestamp.hour + nearest = Enum.min_by(hourly, fn h -> abs((h["utc_hour"] || 0) - qso_hour) end) + %{observation: obs, nearest_hour: nearest} + + _ -> + nil + end + end + defp load_weather(contact) do lat1 = contact.pos1 && contact.pos1["lat"] lon1 = contact.pos1 && (contact.pos1["lon"] || contact.pos1["lng"]) @@ -799,6 +813,47 @@ defmodule MicrowavepropWeb.ContactLive.Show do
+

IEMRE Gridded Reanalysis

+ <%= if @iemre do %> + <% h = @iemre.nearest_hour %> + <% obs = @iemre.observation %> +
+ Grid point: {format_number(obs.lat)}°, {format_number(obs.lon)}° — {obs.date} hour {h[ + "utc_hour" + ] || "—"} UTC +
+
+
Temp: {format_number(h["air_temp_f"])}°F
+
Dewpoint: {format_number(h["dew_point_f"])}°F
+
Sky Cover: {format_number(h["skyc_%"])}%
+
+ Wind: + {format_number(wind_speed_from_components(h["uwnd_mps"], h["vwnd_mps"]))} kts +
+
+ Precip: {format_number(h["hourly_precip_in"])}" +
+
+ Soil Temp: {format_number(h["soil4t_f"])}°F +
+
+ <% else %> +
+ <%= case @contact.iemre_status do %> + <% :queued -> %> + + Fetching IEMRE data + + <% :unavailable -> %> + IEMRE data unavailable for this contact. + <% _ -> %> + No IEMRE data available. + <% end %> +
+ <% end %> + +
+

Surface Observations

<%= if @surface_observations == [] do %>
@@ -931,6 +986,13 @@ defmodule MicrowavepropWeb.ContactLive.Show do defp format_number(n) when is_float(n), do: :erlang.float_to_binary(n, decimals: 1) defp format_number(n), do: to_string(n) + defp wind_speed_from_components(u, v) when is_number(u) and is_number(v) do + mps = :math.sqrt(u * u + v * v) + Float.round(mps * 1.944, 1) + end + + defp wind_speed_from_components(_, _), do: nil + defp format_dist(km) do mi = km * 0.621371 "#{:erlang.float_to_binary(mi, decimals: 1)} mi (#{:erlang.float_to_binary(km, decimals: 1)} km)"