diff --git a/lib/microwaveprop_web/components/skew_t_chart.ex b/lib/microwaveprop_web/components/skew_t_chart.ex
new file mode 100644
index 00000000..44271d6f
--- /dev/null
+++ b/lib/microwaveprop_web/components/skew_t_chart.ex
@@ -0,0 +1,180 @@
+defmodule MicrowavepropWeb.Components.SkewTChart do
+ @moduledoc """
+ Function component that renders a skew-T log-P diagram from a
+ pressure-level profile (list of `%{"pres", "tmpc", "dwpc"}` maps).
+ Returns empty output when the profile has fewer than three usable
+ levels.
+ """
+ use Phoenix.Component
+
+ alias MicrowavepropWeb.SkewT
+
+ attr :profile, :list, required: true
+
+ def skew_t_chart(assigns) do
+ chart = SkewT.build(assigns.profile)
+ usable_points = if chart, do: length(chart.t_points), else: 0
+ assigns = assign(assigns, chart: chart, usable_points: usable_points)
+
+ ~H"""
+
= 2} class="mt-3">
+
+ Skew-T log-P
+
+
+
+ Orange curves: dry adiabats · dashed teal: saturation mixing ratio ·
+ thin gray: isotherms and isobars
+
+
+ """
+ end
+end
diff --git a/lib/microwaveprop_web/live/contact_live/show.ex b/lib/microwaveprop_web/live/contact_live/show.ex
index ffd88896..43848277 100644
--- a/lib/microwaveprop_web/live/contact_live/show.ex
+++ b/lib/microwaveprop_web/live/contact_live/show.ex
@@ -2,6 +2,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do
@moduledoc false
use MicrowavepropWeb, :live_view
+ import MicrowavepropWeb.Components.SkewTChart
+
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Propagation.Scorer
alias Microwaveprop.Radio
@@ -16,7 +18,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do
alias Microwaveprop.Workers.HrrrFetchWorker
alias Microwaveprop.Workers.SolarIndexWorker
alias Microwaveprop.Workers.TerrainProfileWorker
- alias MicrowavepropWeb.SkewT
require Logger
@@ -2171,176 +2172,4 @@ defmodule MicrowavepropWeb.ContactLive.Show do
defp maybe_put_band(params, band) when is_integer(band), do: Map.put(params, "band", Integer.to_string(band))
defp maybe_put_band(params, band) when is_binary(band), do: Map.put(params, "band", band)
defp maybe_put_band(params, _), do: params
-
- # Render a skew-T log-P diagram from a pressure-level profile
- # (list of `%{"pres", "tmpc", "dwpc"}` maps). Returns empty output
- # when the profile has fewer than three usable levels.
- attr :profile, :list, required: true
-
- defp skew_t_chart(assigns) do
- chart = SkewT.build(assigns.profile)
- usable_points = if chart, do: length(chart.t_points), else: 0
- assigns = assign(assigns, chart: chart, usable_points: usable_points)
-
- ~H"""
- = 2} class="mt-3">
-
- Skew-T log-P
-
-
-
- Orange curves: dry adiabats · dashed teal: saturation mixing ratio ·
- thin gray: isotherms and isobars
-
-
- """
- end
end
diff --git a/lib/microwaveprop_web/live/path_live.ex b/lib/microwaveprop_web/live/path_live.ex
index 54f6c384..60757599 100644
--- a/lib/microwaveprop_web/live/path_live.ex
+++ b/lib/microwaveprop_web/live/path_live.ex
@@ -2,6 +2,8 @@ defmodule MicrowavepropWeb.PathLive do
@moduledoc false
use MicrowavepropWeb, :live_view
+ import MicrowavepropWeb.Components.SkewTChart
+
alias Microwaveprop.Propagation
alias Microwaveprop.Propagation.BandConfig
alias Microwaveprop.Propagation.Scorer
@@ -230,11 +232,17 @@ defmodule MicrowavepropWeb.PathLive do
midlat = (src.lat + dst.lat) / 2
midlon = (src.lon + dst.lon) / 2
- hrrr_profiles =
- [{src.lat, src.lon}, {midlat, midlon}, {dst.lat, dst.lon}]
- |> Enum.map(fn {lat, lon} -> Weather.find_nearest_hrrr(lat, lon, now) end)
+ hrrr_points =
+ [
+ {"Source", src.lat, src.lon},
+ {"Midpoint", midlat, midlon},
+ {"Destination", dst.lat, dst.lon}
+ ]
+ |> Enum.map(&label_hrrr_point(&1, now))
|> Enum.reject(&is_nil/1)
+ hrrr_profiles = Enum.map(hrrr_points, & &1.profile)
+
# Build conditions and score
{conditions, scoring} = build_scoring(hrrr_profiles, src, now, band_config)
@@ -261,7 +269,8 @@ defmodule MicrowavepropWeb.PathLive do
loss_budget: loss_budget,
power_budget: power_budget,
forecast: forecast,
- hrrr_count: length(hrrr_profiles)
+ hrrr_count: length(hrrr_profiles),
+ hrrr_points: hrrr_points
}}
end
end
@@ -312,6 +321,13 @@ defmodule MicrowavepropWeb.PathLive do
Regex.match?(~r/^[A-Ra-r]{2}[0-9]{2}/i, input)
end
+ defp label_hrrr_point({label, lat, lon}, now) do
+ case Weather.find_nearest_hrrr(lat, lon, now) do
+ nil -> nil
+ profile -> %{label: label, profile: profile}
+ end
+ end
+
defp build_scoring([], _src, _now, _band_config), do: {nil, nil}
defp build_scoring(profiles, src, now, band_config) do
@@ -845,6 +861,11 @@ defmodule MicrowavepropWeb.PathLive do
<% end %>
+ <%!-- Atmospheric Profile (HRRR) --%>
+ <%= if @result.hrrr_points != [] do %>
+ <.atmospheric_profile hrrr_points={@result.hrrr_points} />
+ <% end %>
+
<%!-- 18-Hour Forecast --%>
<%= if @result.forecast != [] do %>
<.forecast_chart forecast={@result.forecast} band_config={@result.band_config} />
@@ -864,6 +885,87 @@ defmodule MicrowavepropWeb.PathLive do
"""
end
+ attr :hrrr_points, :list, required: true
+
+ defp atmospheric_profile(assigns) do
+ mid = Enum.find(assigns.hrrr_points, &(&1.label == "Midpoint")) || hd(assigns.hrrr_points)
+ valid_time = mid.profile.valid_time
+ run_time = Map.get(mid.profile, :run_time)
+ any_ducting? = Enum.any?(assigns.hrrr_points, & &1.profile.ducting_detected)
+
+ assigns =
+ assigns
+ |> assign(:mid, mid)
+ |> assign(:valid_time, valid_time)
+ |> assign(:run_time, run_time)
+ |> assign(:any_ducting, any_ducting?)
+
+ ~H"""
+
+
+
Atmospheric Profile
+ HRRR (3 km)
+
+ Valid {Calendar.strftime(@valid_time, "%Y-%m-%d %H:%M UTC")}
+
+ <%= if @run_time do %>
+
+ Run {Calendar.strftime(@run_time, "%Y-%m-%d %H:%M UTC")}
+
+ <% end %>
+
+ {length(@hrrr_points)} / 3 path points
+
+ <%= if @any_ducting do %>
+ Ducting detected
+ <% end %>
+
+
+
+
+
+
+ | Point |
+ Lat, Lon |
+ Temp |
+ Dewpt |
+ Press |
+ HPBL |
+ PWAT |
+ Ns |
+ dN/dh |
+
+
+
+ <%= for pt <- @hrrr_points do %>
+
+ | {pt.label} |
+
+ {format_number(pt.profile.lat)}, {format_number(pt.profile.lon)}
+ |
+ {format_number(pt.profile.surface_temp_c)}°C |
+ {format_number(pt.profile.surface_dewpoint_c)}°C |
+ {format_number(pt.profile.surface_pressure_mb)} mb |
+ {format_number(pt.profile.hpbl_m)} m |
+ {format_number(pt.profile.pwat_mm)} mm |
+ {format_number(pt.profile.surface_refractivity)} |
+ {format_number(pt.profile.min_refractivity_gradient)} |
+
+ <% end %>
+
+
+
+
+ <%= if @mid.profile.profile && @mid.profile.profile != [] do %>
+
+ Midpoint vertical profile
+
+ <.skew_t_chart profile={@mid.profile.profile} />
+ <% end %>
+
+ """
+ end
+
attr :forecast, :list, required: true
attr :band_config, :map, required: true