Show IEMRE gridded reanalysis data on contact detail page

This commit is contained in:
Graham McIntire 2026-04-04 11:35:30 -05:00
parent da490f7c59
commit 383c18e50a
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59

View file

@ -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
<div class="divider" />
<h2 class="text-base font-semibold mb-2">IEMRE Gridded Reanalysis</h2>
<%= if @iemre do %>
<% h = @iemre.nearest_hour %>
<% obs = @iemre.observation %>
<div class="text-xs opacity-60 mb-2">
Grid point: {format_number(obs.lat)}°, {format_number(obs.lon)}° &mdash; {obs.date} hour {h[
"utc_hour"
] || ""} UTC
</div>
<div class="flex flex-wrap gap-x-6 gap-y-1 text-sm">
<div><span class="font-semibold">Temp:</span> {format_number(h["air_temp_f"])}°F</div>
<div><span class="font-semibold">Dewpoint:</span> {format_number(h["dew_point_f"])}°F</div>
<div><span class="font-semibold">Sky Cover:</span> {format_number(h["skyc_%"])}%</div>
<div>
<span class="font-semibold">Wind:</span>
{format_number(wind_speed_from_components(h["uwnd_mps"], h["vwnd_mps"]))} kts
</div>
<div>
<span class="font-semibold">Precip:</span> {format_number(h["hourly_precip_in"])}"
</div>
<div :if={h["soil4t_f"]}>
<span class="font-semibold">Soil Temp:</span> {format_number(h["soil4t_f"])}°F
</div>
</div>
<% else %>
<div class="text-sm text-base-content/50 italic">
<%= case @contact.iemre_status do %>
<% :queued -> %>
<span class="flex items-center gap-2">
<span class="loading loading-spinner loading-sm"></span> Fetching IEMRE data
</span>
<% :unavailable -> %>
IEMRE data unavailable for this contact.
<% _ -> %>
No IEMRE data available.
<% end %>
</div>
<% end %>
<div class="divider" />
<h2 class="text-base font-semibold mb-2">Surface Observations</h2>
<%= if @surface_observations == [] do %>
<div class="text-sm text-base-content/50 italic">
@ -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)"