Surface HRRR weather details on /path
Adds an Atmospheric Profile section to the link calculator that shows the HRRR data actually feeding the prediction: valid/run time, a per-point table (src/mid/dst) with surface temp, dewpoint, pressure, HPBL, PWAT, surface refractivity and dN/dh, plus a skew-T log-P chart for the midpoint vertical profile. Pulls the skew-T chart function out of contact_live into a shared component so both LiveViews can render it without duplication.
This commit is contained in:
parent
84ffdcd261
commit
c3616b31c7
3 changed files with 288 additions and 177 deletions
180
lib/microwaveprop_web/components/skew_t_chart.ex
Normal file
180
lib/microwaveprop_web/components/skew_t_chart.ex
Normal file
|
|
@ -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"""
|
||||||
|
<div :if={@chart && @usable_points >= 2} class="mt-3">
|
||||||
|
<div class="text-xs font-semibold uppercase tracking-wider opacity-60 mb-1">
|
||||||
|
Skew-T log-P
|
||||||
|
</div>
|
||||||
|
<svg
|
||||||
|
viewBox={"0 0 #{@chart.width} #{@chart.height}"}
|
||||||
|
class="w-full max-w-[600px] bg-base-100 rounded border border-base-300"
|
||||||
|
role="img"
|
||||||
|
aria-label="Skew-T log-P diagram"
|
||||||
|
>
|
||||||
|
<%!-- Plot background --%>
|
||||||
|
<rect
|
||||||
|
x={@chart.padding_left}
|
||||||
|
y={@chart.padding_top}
|
||||||
|
width={@chart.plot_width}
|
||||||
|
height={@chart.plot_height}
|
||||||
|
fill="currentColor"
|
||||||
|
class="text-base-200/40"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Clip background curves to the plot area so skewed lines
|
||||||
|
don't bleed over the axes. --%>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="skewt-clip">
|
||||||
|
<rect
|
||||||
|
x={@chart.padding_left}
|
||||||
|
y={@chart.padding_top}
|
||||||
|
width={@chart.plot_width}
|
||||||
|
height={@chart.plot_height}
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<g clip-path="url(#skewt-clip)">
|
||||||
|
<%!-- Dry adiabats (orange curves) --%>
|
||||||
|
<path
|
||||||
|
:for={adiabat <- @chart.dry_adiabats}
|
||||||
|
d={adiabat.path}
|
||||||
|
fill="none"
|
||||||
|
stroke="#f59e0b"
|
||||||
|
stroke-width="0.6"
|
||||||
|
stroke-opacity="0.45"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Mixing ratio lines (dashed teal) --%>
|
||||||
|
<path
|
||||||
|
:for={line <- @chart.mixing_ratio_lines}
|
||||||
|
d={line.path}
|
||||||
|
fill="none"
|
||||||
|
stroke="#0d9488"
|
||||||
|
stroke-width="0.6"
|
||||||
|
stroke-opacity="0.5"
|
||||||
|
stroke-dasharray="2 3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Isotherms (gray) --%>
|
||||||
|
<line
|
||||||
|
:for={iso <- @chart.isotherms}
|
||||||
|
x1={iso.x1}
|
||||||
|
y1={iso.y1}
|
||||||
|
x2={iso.x2}
|
||||||
|
y2={iso.y2}
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="0.5"
|
||||||
|
class="text-base-content/25"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Isobars (gray, across plot) --%>
|
||||||
|
<line
|
||||||
|
:for={bar <- @chart.isobars}
|
||||||
|
x1={bar.x_start}
|
||||||
|
y1={bar.y}
|
||||||
|
x2={bar.x_end}
|
||||||
|
y2={bar.y}
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="0.5"
|
||||||
|
class="text-base-content/25"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Dewpoint trace --%>
|
||||||
|
<path
|
||||||
|
d={@chart.td_path}
|
||||||
|
fill="none"
|
||||||
|
stroke={@chart.dewpoint_color}
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
:for={{x, y} <- @chart.td_points}
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r="2"
|
||||||
|
fill={@chart.dewpoint_color}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<%!-- Temperature trace --%>
|
||||||
|
<path
|
||||||
|
d={@chart.t_path}
|
||||||
|
fill="none"
|
||||||
|
stroke={@chart.temp_color}
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
:for={{x, y} <- @chart.t_points}
|
||||||
|
cx={x}
|
||||||
|
cy={y}
|
||||||
|
r="2"
|
||||||
|
fill={@chart.temp_color}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
<%!-- Pressure axis labels (left side) --%>
|
||||||
|
<text
|
||||||
|
:for={bar <- @chart.isobars}
|
||||||
|
x={@chart.padding_left - 6}
|
||||||
|
y={bar.label_y}
|
||||||
|
font-size="10"
|
||||||
|
text-anchor="end"
|
||||||
|
fill="currentColor"
|
||||||
|
class="text-base-content/60"
|
||||||
|
>
|
||||||
|
{bar.label}
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<%!-- Temperature axis labels (bottom, only those inside the visible band) --%>
|
||||||
|
<text
|
||||||
|
:for={iso <- @chart.isotherms}
|
||||||
|
:if={iso.label_x && iso.t_c >= -40 && iso.t_c <= 40 && rem(iso.t_c, 20) == 0}
|
||||||
|
x={iso.label_x}
|
||||||
|
y={iso.label_y}
|
||||||
|
font-size="10"
|
||||||
|
text-anchor="middle"
|
||||||
|
fill="currentColor"
|
||||||
|
class="text-base-content/60"
|
||||||
|
>
|
||||||
|
{iso.t_c}°C
|
||||||
|
</text>
|
||||||
|
|
||||||
|
<%!-- Legend --%>
|
||||||
|
<g transform={"translate(#{@chart.padding_left + 8}, #{@chart.padding_top + 12})"}>
|
||||||
|
<line x1="0" y1="0" x2="18" y2="0" stroke={@chart.temp_color} stroke-width="2" />
|
||||||
|
<text x="22" y="3" font-size="10" fill="currentColor" class="text-base-content/70">
|
||||||
|
Temperature
|
||||||
|
</text>
|
||||||
|
<line x1="0" y1="14" x2="18" y2="14" stroke={@chart.dewpoint_color} stroke-width="2" />
|
||||||
|
<text x="22" y="17" font-size="10" fill="currentColor" class="text-base-content/70">
|
||||||
|
Dewpoint
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="text-[11px] opacity-60 mt-1">
|
||||||
|
Orange curves: dry adiabats · dashed teal: saturation mixing ratio ·
|
||||||
|
thin gray: isotherms and isobars
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -2,6 +2,8 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use MicrowavepropWeb, :live_view
|
use MicrowavepropWeb, :live_view
|
||||||
|
|
||||||
|
import MicrowavepropWeb.Components.SkewTChart
|
||||||
|
|
||||||
alias Microwaveprop.Propagation.BandConfig
|
alias Microwaveprop.Propagation.BandConfig
|
||||||
alias Microwaveprop.Propagation.Scorer
|
alias Microwaveprop.Propagation.Scorer
|
||||||
alias Microwaveprop.Radio
|
alias Microwaveprop.Radio
|
||||||
|
|
@ -16,7 +18,6 @@ defmodule MicrowavepropWeb.ContactLive.Show do
|
||||||
alias Microwaveprop.Workers.HrrrFetchWorker
|
alias Microwaveprop.Workers.HrrrFetchWorker
|
||||||
alias Microwaveprop.Workers.SolarIndexWorker
|
alias Microwaveprop.Workers.SolarIndexWorker
|
||||||
alias Microwaveprop.Workers.TerrainProfileWorker
|
alias Microwaveprop.Workers.TerrainProfileWorker
|
||||||
alias MicrowavepropWeb.SkewT
|
|
||||||
|
|
||||||
require Logger
|
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_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, band) when is_binary(band), do: Map.put(params, "band", band)
|
||||||
defp maybe_put_band(params, _), do: params
|
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"""
|
|
||||||
<div :if={@chart && @usable_points >= 2} class="mt-3">
|
|
||||||
<div class="text-xs font-semibold uppercase tracking-wider opacity-60 mb-1">
|
|
||||||
Skew-T log-P
|
|
||||||
</div>
|
|
||||||
<svg
|
|
||||||
viewBox={"0 0 #{@chart.width} #{@chart.height}"}
|
|
||||||
class="w-full max-w-[600px] bg-base-100 rounded border border-base-300"
|
|
||||||
role="img"
|
|
||||||
aria-label="Skew-T log-P diagram"
|
|
||||||
>
|
|
||||||
<%!-- Plot background --%>
|
|
||||||
<rect
|
|
||||||
x={@chart.padding_left}
|
|
||||||
y={@chart.padding_top}
|
|
||||||
width={@chart.plot_width}
|
|
||||||
height={@chart.plot_height}
|
|
||||||
fill="currentColor"
|
|
||||||
class="text-base-200/40"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Clip background curves to the plot area so skewed lines
|
|
||||||
don't bleed over the axes. --%>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="skewt-clip">
|
|
||||||
<rect
|
|
||||||
x={@chart.padding_left}
|
|
||||||
y={@chart.padding_top}
|
|
||||||
width={@chart.plot_width}
|
|
||||||
height={@chart.plot_height}
|
|
||||||
/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
|
|
||||||
<g clip-path="url(#skewt-clip)">
|
|
||||||
<%!-- Dry adiabats (orange curves) --%>
|
|
||||||
<path
|
|
||||||
:for={adiabat <- @chart.dry_adiabats}
|
|
||||||
d={adiabat.path}
|
|
||||||
fill="none"
|
|
||||||
stroke="#f59e0b"
|
|
||||||
stroke-width="0.6"
|
|
||||||
stroke-opacity="0.45"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Mixing ratio lines (dashed teal) --%>
|
|
||||||
<path
|
|
||||||
:for={line <- @chart.mixing_ratio_lines}
|
|
||||||
d={line.path}
|
|
||||||
fill="none"
|
|
||||||
stroke="#0d9488"
|
|
||||||
stroke-width="0.6"
|
|
||||||
stroke-opacity="0.5"
|
|
||||||
stroke-dasharray="2 3"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Isotherms (gray) --%>
|
|
||||||
<line
|
|
||||||
:for={iso <- @chart.isotherms}
|
|
||||||
x1={iso.x1}
|
|
||||||
y1={iso.y1}
|
|
||||||
x2={iso.x2}
|
|
||||||
y2={iso.y2}
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="0.5"
|
|
||||||
class="text-base-content/25"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Isobars (gray, across plot) --%>
|
|
||||||
<line
|
|
||||||
:for={bar <- @chart.isobars}
|
|
||||||
x1={bar.x_start}
|
|
||||||
y1={bar.y}
|
|
||||||
x2={bar.x_end}
|
|
||||||
y2={bar.y}
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-width="0.5"
|
|
||||||
class="text-base-content/25"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Dewpoint trace --%>
|
|
||||||
<path
|
|
||||||
d={@chart.td_path}
|
|
||||||
fill="none"
|
|
||||||
stroke={@chart.dewpoint_color}
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
:for={{x, y} <- @chart.td_points}
|
|
||||||
cx={x}
|
|
||||||
cy={y}
|
|
||||||
r="2"
|
|
||||||
fill={@chart.dewpoint_color}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<%!-- Temperature trace --%>
|
|
||||||
<path
|
|
||||||
d={@chart.t_path}
|
|
||||||
fill="none"
|
|
||||||
stroke={@chart.temp_color}
|
|
||||||
stroke-width="2"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
/>
|
|
||||||
<circle
|
|
||||||
:for={{x, y} <- @chart.t_points}
|
|
||||||
cx={x}
|
|
||||||
cy={y}
|
|
||||||
r="2"
|
|
||||||
fill={@chart.temp_color}
|
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
|
|
||||||
<%!-- Pressure axis labels (left side) --%>
|
|
||||||
<text
|
|
||||||
:for={bar <- @chart.isobars}
|
|
||||||
x={@chart.padding_left - 6}
|
|
||||||
y={bar.label_y}
|
|
||||||
font-size="10"
|
|
||||||
text-anchor="end"
|
|
||||||
fill="currentColor"
|
|
||||||
class="text-base-content/60"
|
|
||||||
>
|
|
||||||
{bar.label}
|
|
||||||
</text>
|
|
||||||
|
|
||||||
<%!-- Temperature axis labels (bottom, only those inside the visible band) --%>
|
|
||||||
<text
|
|
||||||
:for={iso <- @chart.isotherms}
|
|
||||||
:if={iso.label_x && iso.t_c >= -40 && iso.t_c <= 40 && rem(iso.t_c, 20) == 0}
|
|
||||||
x={iso.label_x}
|
|
||||||
y={iso.label_y}
|
|
||||||
font-size="10"
|
|
||||||
text-anchor="middle"
|
|
||||||
fill="currentColor"
|
|
||||||
class="text-base-content/60"
|
|
||||||
>
|
|
||||||
{iso.t_c}°C
|
|
||||||
</text>
|
|
||||||
|
|
||||||
<%!-- Legend --%>
|
|
||||||
<g transform={"translate(#{@chart.padding_left + 8}, #{@chart.padding_top + 12})"}>
|
|
||||||
<line x1="0" y1="0" x2="18" y2="0" stroke={@chart.temp_color} stroke-width="2" />
|
|
||||||
<text x="22" y="3" font-size="10" fill="currentColor" class="text-base-content/70">
|
|
||||||
Temperature
|
|
||||||
</text>
|
|
||||||
<line x1="0" y1="14" x2="18" y2="14" stroke={@chart.dewpoint_color} stroke-width="2" />
|
|
||||||
<text x="22" y="17" font-size="10" fill="currentColor" class="text-base-content/70">
|
|
||||||
Dewpoint
|
|
||||||
</text>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
<div class="text-[11px] opacity-60 mt-1">
|
|
||||||
Orange curves: dry adiabats · dashed teal: saturation mixing ratio ·
|
|
||||||
thin gray: isotherms and isobars
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use MicrowavepropWeb, :live_view
|
use MicrowavepropWeb, :live_view
|
||||||
|
|
||||||
|
import MicrowavepropWeb.Components.SkewTChart
|
||||||
|
|
||||||
alias Microwaveprop.Propagation
|
alias Microwaveprop.Propagation
|
||||||
alias Microwaveprop.Propagation.BandConfig
|
alias Microwaveprop.Propagation.BandConfig
|
||||||
alias Microwaveprop.Propagation.Scorer
|
alias Microwaveprop.Propagation.Scorer
|
||||||
|
|
@ -230,11 +232,17 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
midlat = (src.lat + dst.lat) / 2
|
midlat = (src.lat + dst.lat) / 2
|
||||||
midlon = (src.lon + dst.lon) / 2
|
midlon = (src.lon + dst.lon) / 2
|
||||||
|
|
||||||
hrrr_profiles =
|
hrrr_points =
|
||||||
[{src.lat, src.lon}, {midlat, midlon}, {dst.lat, dst.lon}]
|
[
|
||||||
|> Enum.map(fn {lat, lon} -> Weather.find_nearest_hrrr(lat, lon, now) end)
|
{"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)
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
|
hrrr_profiles = Enum.map(hrrr_points, & &1.profile)
|
||||||
|
|
||||||
# Build conditions and score
|
# Build conditions and score
|
||||||
{conditions, scoring} = build_scoring(hrrr_profiles, src, now, band_config)
|
{conditions, scoring} = build_scoring(hrrr_profiles, src, now, band_config)
|
||||||
|
|
||||||
|
|
@ -261,7 +269,8 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
loss_budget: loss_budget,
|
loss_budget: loss_budget,
|
||||||
power_budget: power_budget,
|
power_budget: power_budget,
|
||||||
forecast: forecast,
|
forecast: forecast,
|
||||||
hrrr_count: length(hrrr_profiles)
|
hrrr_count: length(hrrr_profiles),
|
||||||
|
hrrr_points: hrrr_points
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -312,6 +321,13 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
Regex.match?(~r/^[A-Ra-r]{2}[0-9]{2}/i, input)
|
Regex.match?(~r/^[A-Ra-r]{2}[0-9]{2}/i, input)
|
||||||
end
|
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([], _src, _now, _band_config), do: {nil, nil}
|
||||||
|
|
||||||
defp build_scoring(profiles, src, now, band_config) do
|
defp build_scoring(profiles, src, now, band_config) do
|
||||||
|
|
@ -845,6 +861,11 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<%!-- Atmospheric Profile (HRRR) --%>
|
||||||
|
<%= if @result.hrrr_points != [] do %>
|
||||||
|
<.atmospheric_profile hrrr_points={@result.hrrr_points} />
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<%!-- 18-Hour Forecast --%>
|
<%!-- 18-Hour Forecast --%>
|
||||||
<%= if @result.forecast != [] do %>
|
<%= if @result.forecast != [] do %>
|
||||||
<.forecast_chart forecast={@result.forecast} band_config={@result.band_config} />
|
<.forecast_chart forecast={@result.forecast} band_config={@result.band_config} />
|
||||||
|
|
@ -864,6 +885,87 @@ defmodule MicrowavepropWeb.PathLive do
|
||||||
"""
|
"""
|
||||||
end
|
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"""
|
||||||
|
<div class="bg-base-200 rounded-box p-4 mb-6">
|
||||||
|
<div class="flex flex-wrap items-center gap-x-4 gap-y-2 mb-3">
|
||||||
|
<h3 class="text-base font-semibold">Atmospheric Profile</h3>
|
||||||
|
<span class="badge badge-outline badge-sm whitespace-nowrap">HRRR (3 km)</span>
|
||||||
|
<span class="text-xs opacity-60 whitespace-nowrap">
|
||||||
|
Valid {Calendar.strftime(@valid_time, "%Y-%m-%d %H:%M UTC")}
|
||||||
|
</span>
|
||||||
|
<%= if @run_time do %>
|
||||||
|
<span class="text-xs opacity-60 whitespace-nowrap">
|
||||||
|
Run {Calendar.strftime(@run_time, "%Y-%m-%d %H:%M UTC")}
|
||||||
|
</span>
|
||||||
|
<% end %>
|
||||||
|
<span class="text-xs opacity-60 whitespace-nowrap">
|
||||||
|
{length(@hrrr_points)} / 3 path points
|
||||||
|
</span>
|
||||||
|
<%= if @any_ducting do %>
|
||||||
|
<span class="badge badge-warning badge-sm whitespace-nowrap">Ducting detected</span>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="table table-xs table-zebra">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Point</th>
|
||||||
|
<th>Lat, Lon</th>
|
||||||
|
<th>Temp</th>
|
||||||
|
<th>Dewpt</th>
|
||||||
|
<th>Press</th>
|
||||||
|
<th>HPBL</th>
|
||||||
|
<th>PWAT</th>
|
||||||
|
<th>N<sub>s</sub></th>
|
||||||
|
<th>dN/dh</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<%= for pt <- @hrrr_points do %>
|
||||||
|
<tr>
|
||||||
|
<td class="font-semibold">{pt.label}</td>
|
||||||
|
<td class="font-mono opacity-70">
|
||||||
|
{format_number(pt.profile.lat)}, {format_number(pt.profile.lon)}
|
||||||
|
</td>
|
||||||
|
<td>{format_number(pt.profile.surface_temp_c)}°C</td>
|
||||||
|
<td>{format_number(pt.profile.surface_dewpoint_c)}°C</td>
|
||||||
|
<td>{format_number(pt.profile.surface_pressure_mb)} mb</td>
|
||||||
|
<td>{format_number(pt.profile.hpbl_m)} m</td>
|
||||||
|
<td>{format_number(pt.profile.pwat_mm)} mm</td>
|
||||||
|
<td>{format_number(pt.profile.surface_refractivity)}</td>
|
||||||
|
<td>{format_number(pt.profile.min_refractivity_gradient)}</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= if @mid.profile.profile && @mid.profile.profile != [] do %>
|
||||||
|
<div class="text-xs opacity-60 mt-4">
|
||||||
|
Midpoint vertical profile
|
||||||
|
</div>
|
||||||
|
<.skew_t_chart profile={@mid.profile.profile} />
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
attr :forecast, :list, required: true
|
attr :forecast, :list, required: true
|
||||||
attr :band_config, :map, required: true
|
attr :band_config, :map, required: true
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue