Fix QSO show page weather lookup and add expandable sounding profiles
Fix pos1 longitude key mismatch (lng vs lon) that caused empty weather results for all QSOs. Bump search radius to 300km for sounding coverage. Soundings now show as expandable cards with full elevation profile data.
This commit is contained in:
parent
c11e6f06f4
commit
480480bde3
1 changed files with 88 additions and 32 deletions
|
|
@ -22,7 +22,8 @@ defmodule MicrowavepropWeb.QsoLive.Show do
|
|||
obs_sort_by: "station_name",
|
||||
obs_sort_order: "asc",
|
||||
sounding_sort_by: "station_name",
|
||||
sounding_sort_order: "asc"
|
||||
sounding_sort_order: "asc",
|
||||
expanded_soundings: MapSet.new()
|
||||
)}
|
||||
end
|
||||
|
||||
|
|
@ -41,6 +42,17 @@ defmodule MicrowavepropWeb.QsoLive.Show do
|
|||
)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_profile", %{"id" => id}, socket) do
|
||||
expanded =
|
||||
if MapSet.member?(socket.assigns.expanded_soundings, id) do
|
||||
MapSet.delete(socket.assigns.expanded_soundings, id)
|
||||
else
|
||||
MapSet.put(socket.assigns.expanded_soundings, id)
|
||||
end
|
||||
|
||||
{:noreply, assign(socket, expanded_soundings: expanded)}
|
||||
end
|
||||
|
||||
def handle_event("sort", %{"field" => field, "table" => "soundings"}, socket) do
|
||||
{sort_by, sort_order} =
|
||||
toggle_sort(socket.assigns.sounding_sort_by, socket.assigns.sounding_sort_order, field)
|
||||
|
|
@ -91,12 +103,15 @@ defmodule MicrowavepropWeb.QsoLive.Show do
|
|||
defp sounding_sort_key(_), do: fn s -> s.station.name || s.station.station_code end
|
||||
|
||||
defp load_weather(qso) do
|
||||
case qso.pos1 do
|
||||
%{"lat" => lat, "lon" => lon} ->
|
||||
Weather.weather_for_qso(%{lat: lat, lon: lon, timestamp: qso.qso_timestamp})
|
||||
lat = qso.pos1 && qso.pos1["lat"]
|
||||
lon = qso.pos1 && (qso.pos1["lon"] || qso.pos1["lng"])
|
||||
|
||||
_ ->
|
||||
%{surface_observations: [], soundings: []}
|
||||
if lat && lon do
|
||||
Weather.weather_for_qso(%{lat: lat, lon: lon, timestamp: qso.qso_timestamp},
|
||||
radius_km: 300
|
||||
)
|
||||
else
|
||||
%{surface_observations: [], soundings: []}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -175,32 +190,67 @@ defmodule MicrowavepropWeb.QsoLive.Show do
|
|||
<%= if @soundings == [] do %>
|
||||
<p class="text-sm text-base-content/50 italic">No soundings found nearby.</p>
|
||||
<% else %>
|
||||
<.table
|
||||
id="soundings"
|
||||
rows={@soundings}
|
||||
row_id={fn s -> "sounding-#{s.id}" end}
|
||||
sort_by={@sounding_sort_by}
|
||||
sort_order={@sounding_sort_order}
|
||||
sort_target="soundings"
|
||||
>
|
||||
<:col :let={s} label="Station" sort_field="station_name">
|
||||
{s.station.name || s.station.station_code}
|
||||
</:col>
|
||||
<:col :let={s} label="Time" sort_field="observed_at">
|
||||
{Calendar.strftime(s.observed_at, "%H:%M")}
|
||||
</:col>
|
||||
<:col :let={s} label="Sfc Temp (C)" sort_field="surface_temp_c">
|
||||
{s.surface_temp_c || "—"}
|
||||
</:col>
|
||||
<:col :let={s} label="Refractivity" sort_field="surface_refractivity">
|
||||
{format_number(s.surface_refractivity)}
|
||||
</:col>
|
||||
<:col :let={s} label="Ducting">{if s.ducting_detected, do: "Yes", else: "No"}</:col>
|
||||
<:col :let={s} label="K-Index" sort_field="k_index">{format_number(s.k_index)}</:col>
|
||||
<:col :let={s} label="Lifted Index" sort_field="lifted_index">
|
||||
{format_number(s.lifted_index)}
|
||||
</:col>
|
||||
</.table>
|
||||
<%= for s <- @soundings do %>
|
||||
<div class="mb-6 border border-base-300 rounded-lg">
|
||||
<button
|
||||
phx-click="toggle_profile"
|
||||
phx-value-id={s.id}
|
||||
class="w-full flex items-center justify-between p-4 hover:bg-base-200 transition-colors"
|
||||
>
|
||||
<div class="flex items-center gap-4 text-sm">
|
||||
<span class="font-semibold">{s.station.name || s.station.station_code}</span>
|
||||
<span>{Calendar.strftime(s.observed_at, "%H:%M UTC")}</span>
|
||||
<span>{s.level_count} levels</span>
|
||||
<span>Sfc: {format_number(s.surface_temp_c)}°C</span>
|
||||
<span>N: {format_number(s.surface_refractivity)}</span>
|
||||
<span>K: {format_number(s.k_index)}</span>
|
||||
<span>LI: {format_number(s.lifted_index)}</span>
|
||||
<%= if s.ducting_detected do %>
|
||||
<span class="badge badge-warning badge-sm">Ducting</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<.icon
|
||||
name={
|
||||
if MapSet.member?(@expanded_soundings, s.id),
|
||||
do: "hero-chevron-up",
|
||||
else: "hero-chevron-down"
|
||||
}
|
||||
class="w-5 h-5"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<%= if MapSet.member?(@expanded_soundings, s.id) do %>
|
||||
<div class="px-4 pb-4">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-xs table-zebra">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Pressure (mb)</th>
|
||||
<th>Height (m)</th>
|
||||
<th>Temp (°C)</th>
|
||||
<th>Dewpoint (°C)</th>
|
||||
<th>Wind Dir (°)</th>
|
||||
<th>Wind Spd (kts)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<%= for level <- filter_profile(s.profile) do %>
|
||||
<tr>
|
||||
<td>{format_number(level["pres"])}</td>
|
||||
<td>{format_number(level["hght"])}</td>
|
||||
<td>{format_number(level["tmpc"])}</td>
|
||||
<td>{format_number(level["dwpc"])}</td>
|
||||
<td>{format_number(level["drct"])}</td>
|
||||
<td>{format_number(level["sknt"])}</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="divider" />
|
||||
|
|
@ -231,6 +281,12 @@ defmodule MicrowavepropWeb.QsoLive.Show do
|
|||
end
|
||||
end
|
||||
|
||||
defp filter_profile(profile) do
|
||||
Enum.filter(profile, fn level ->
|
||||
level["tmpc"] != nil || level["dwpc"] != nil
|
||||
end)
|
||||
end
|
||||
|
||||
defp format_kp(nil), do: "—"
|
||||
defp format_kp(values) when is_list(values), do: Enum.map_join(values, ", ", &format_number/1)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue