feat(rover): URL station persistence + 10-digit grids on calculated values
This commit is contained in:
parent
150762d21d
commit
c5d6633beb
3 changed files with 124 additions and 27 deletions
|
|
@ -48,11 +48,11 @@ defmodule Microwaveprop.Radio.Maidenhead do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converts a lat/lon pair into a Maidenhead grid at the requested
|
Converts a lat/lon pair into a Maidenhead grid at the requested
|
||||||
precision (must be an even number between 4 and 8). Default precision
|
precision (must be an even number between 4 and 10). Default precision
|
||||||
is 6 (subsquare level, ~5'×2.5', ~7 km × 4.6 km at the equator).
|
is 6 (subsquare level, ~5'×2.5', ~7 km × 4.6 km at the equator).
|
||||||
"""
|
"""
|
||||||
@spec from_latlon(number(), number(), pos_integer()) :: String.t()
|
@spec from_latlon(number(), number(), pos_integer()) :: String.t()
|
||||||
def from_latlon(lat, lon, precision \\ 6) when is_number(lat) and is_number(lon) and precision in [4, 6, 8] do
|
def from_latlon(lat, lon, precision \\ 6) when is_number(lat) and is_number(lon) and precision in [4, 6, 8, 10] do
|
||||||
# Clamp into the open ranges the field encoder expects. Exactly
|
# Clamp into the open ranges the field encoder expects. Exactly
|
||||||
# `90.0` / `180.0` would otherwise overflow the trunc into 'S'
|
# `90.0` / `180.0` would otherwise overflow the trunc into 'S'
|
||||||
# (one past 'R'), producing grids outside the A..R alphabet.
|
# (one past 'R'), producing grids outside the A..R alphabet.
|
||||||
|
|
@ -73,26 +73,37 @@ defmodule Microwaveprop.Radio.Maidenhead do
|
||||||
|
|
||||||
grid = <<?A + f1, ?A + f2, ?0 + s1, ?0 + s2>>
|
grid = <<?A + f1, ?A + f2, ?0 + s1, ?0 + s2>>
|
||||||
|
|
||||||
cond do
|
if precision == 4 do
|
||||||
precision == 4 ->
|
grid
|
||||||
grid
|
else
|
||||||
|
# Subsquare (a-x), 5'/60 lon, 2.5'/60 lat
|
||||||
|
ss1 = trunc(lon * 60.0 / 5.0)
|
||||||
|
ss2 = trunc(lat * 60.0 / 2.5)
|
||||||
|
lon = lon - ss1 * (5.0 / 60.0)
|
||||||
|
lat = lat - ss2 * (2.5 / 60.0)
|
||||||
|
grid = grid <> <<?a + ss1, ?a + ss2>>
|
||||||
|
|
||||||
precision >= 6 ->
|
append_extended(grid, lat, lon, precision)
|
||||||
# Subsquare (a-x), 5'/60 lon, 2.5'/60 lat
|
end
|
||||||
ss1 = trunc(lon * 60.0 / 5.0)
|
end
|
||||||
ss2 = trunc(lat * 60.0 / 2.5)
|
|
||||||
lon = lon - ss1 * (5.0 / 60.0)
|
|
||||||
lat = lat - ss2 * (2.5 / 60.0)
|
|
||||||
grid = grid <> <<?a + ss1, ?a + ss2>>
|
|
||||||
|
|
||||||
if precision == 8 do
|
defp append_extended(grid, _lat, _lon, 6), do: grid
|
||||||
# Extended square (0-9), (5/60)/10 lon, (2.5/60)/10 lat
|
|
||||||
e1 = trunc(lon * 60.0 / 0.5)
|
defp append_extended(grid, lat, lon, precision) when precision in [8, 10] do
|
||||||
e2 = trunc(lat * 60.0 / 0.25)
|
# Extended square (0-9), (5'/60)/10 lon, (2.5'/60)/10 lat
|
||||||
grid <> <<?0 + e1, ?0 + e2>>
|
e1 = trunc(lon * 60.0 * 10.0 / 5.0)
|
||||||
else
|
e2 = trunc(lat * 60.0 * 10.0 / 2.5)
|
||||||
grid
|
lon = lon - e1 * (5.0 / 60.0 / 10.0)
|
||||||
end
|
lat = lat - e2 * (2.5 / 60.0 / 10.0)
|
||||||
|
grid = grid <> <<?0 + e1, ?0 + e2>>
|
||||||
|
|
||||||
|
if precision == 8 do
|
||||||
|
grid
|
||||||
|
else
|
||||||
|
# Extended-extended subsquare (a-x), step is the extended-square step / 24
|
||||||
|
x1 = trunc(lon * 60.0 * 10.0 * 24.0 / 5.0)
|
||||||
|
x2 = trunc(lat * 60.0 * 10.0 * 24.0 / 2.5)
|
||||||
|
grid <> <<?a + x1, ?a + x2>>
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ defmodule Microwaveprop.Rover.Compute do
|
||||||
defp candidate_payload(cell, home) do
|
defp candidate_payload(cell, home) do
|
||||||
drive_min = DriveTime.drive_min(cell.distance_km)
|
drive_min = DriveTime.drive_min(cell.distance_km)
|
||||||
bearing = DriveTime.bearing_compass({home.lat, home.lon}, {cell.lat, cell.lon})
|
bearing = DriveTime.bearing_compass({home.lat, home.lon}, {cell.lat, cell.lon})
|
||||||
grid = Maidenhead.from_latlon(cell.lat, cell.lon, 6)
|
grid = Maidenhead.from_latlon(cell.lat, cell.lon, 10)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
grid: grid,
|
grid: grid,
|
||||||
|
|
|
||||||
|
|
@ -59,10 +59,65 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
top_candidates: [],
|
top_candidates: [],
|
||||||
station_form_error: nil,
|
station_form_error: nil,
|
||||||
home_form_error: nil,
|
home_form_error: nil,
|
||||||
scoring_loading: false
|
scoring_loading: false,
|
||||||
|
url_loaded?: false
|
||||||
)}
|
)}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def handle_params(params, _uri, socket) do
|
||||||
|
if socket.assigns.url_loaded? do
|
||||||
|
{:noreply, socket}
|
||||||
|
else
|
||||||
|
socket =
|
||||||
|
socket
|
||||||
|
|> apply_url_stations(params)
|
||||||
|
|> assign(url_loaded?: true)
|
||||||
|
|
||||||
|
{:noreply, socket}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_url_stations(%{assigns: %{persisted?: true}} = socket, _params), do: socket
|
||||||
|
|
||||||
|
defp apply_url_stations(socket, %{"stations" => stations_str}) when is_binary(stations_str) do
|
||||||
|
stations = parse_url_stations(stations_str)
|
||||||
|
if stations == [], do: socket, else: assign(socket, fixed_stations: stations)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_url_stations(socket, _params), do: socket
|
||||||
|
|
||||||
|
defp parse_url_stations(str) do
|
||||||
|
str
|
||||||
|
|> String.split(",", trim: true)
|
||||||
|
|> Enum.with_index()
|
||||||
|
|> Enum.flat_map(fn {entry, idx} -> entry |> parse_url_station_entry(idx) |> List.wrap() end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp parse_url_station_entry("-" <> rest, idx), do: parse_url_station_pair(rest, idx, false)
|
||||||
|
defp parse_url_station_entry(entry, idx), do: parse_url_station_pair(entry, idx, true)
|
||||||
|
|
||||||
|
defp parse_url_station_pair(entry, idx, selected?) do
|
||||||
|
with [call, grid] <- String.split(entry, ":", parts: 2),
|
||||||
|
call = String.upcase(String.trim(call)),
|
||||||
|
grid = String.trim(grid),
|
||||||
|
true <- call != "",
|
||||||
|
{:ok, {lat, lon}} <- Maidenhead.to_latlon(grid) do
|
||||||
|
%{
|
||||||
|
id: "url-#{idx}",
|
||||||
|
callsign: call,
|
||||||
|
grid: grid,
|
||||||
|
lat: lat,
|
||||||
|
lon: lon,
|
||||||
|
elevation_m: nil,
|
||||||
|
selected: selected?,
|
||||||
|
position: idx
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# ── Lifecycle helpers ────────────────────────────────────────────────
|
# ── Lifecycle helpers ────────────────────────────────────────────────
|
||||||
|
|
||||||
defp load_stations(socket) do
|
defp load_stations(socket) do
|
||||||
|
|
@ -104,7 +159,7 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp home_label(lat, lon), do: Maidenhead.from_latlon(lat, lon, 6)
|
defp home_label(lat, lon), do: Maidenhead.from_latlon(lat, lon, 10)
|
||||||
|
|
||||||
defp drive_radius_km(max_drive_min), do: max_drive_min * @avg_speed_kmh / 60.0
|
defp drive_radius_km(max_drive_min), do: max_drive_min * @avg_speed_kmh / 60.0
|
||||||
|
|
||||||
|
|
@ -146,7 +201,10 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
|
|
||||||
case create_station(socket, attrs) do
|
case create_station(socket, attrs) do
|
||||||
{:ok, stations} ->
|
{:ok, stations} ->
|
||||||
{:noreply, assign(socket, fixed_stations: stations, station_form_error: nil)}
|
{:noreply,
|
||||||
|
socket
|
||||||
|
|> assign(fixed_stations: stations, station_form_error: nil)
|
||||||
|
|> sync_url()}
|
||||||
|
|
||||||
{:error, msg} ->
|
{:error, msg} ->
|
||||||
{:noreply, assign(socket, station_form_error: msg)}
|
{:noreply, assign(socket, station_form_error: msg)}
|
||||||
|
|
@ -206,11 +264,39 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
|
|
||||||
defp handle_station_mutation(socket, id, fun) do
|
defp handle_station_mutation(socket, id, fun) do
|
||||||
case fun.(socket, id) do
|
case fun.(socket, id) do
|
||||||
{:ok, stations} -> {:noreply, assign(socket, fixed_stations: stations)}
|
{:ok, stations} ->
|
||||||
{:error, _} -> {:noreply, socket}
|
{:noreply, socket |> assign(fixed_stations: stations) |> sync_url()}
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp sync_url(socket) do
|
||||||
|
push_patch(socket, to: rover_path(socket), replace: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp rover_path(socket) do
|
||||||
|
case build_url_stations(socket.assigns.fixed_stations) do
|
||||||
|
"" -> ~p"/rover"
|
||||||
|
str -> ~p"/rover?#{[stations: str]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_url_stations(stations) do
|
||||||
|
stations
|
||||||
|
|> Enum.map(&encode_url_station/1)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|> Enum.join(",")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp encode_url_station(%{callsign: call, grid: grid, selected: sel}) when is_binary(grid) and grid != "" do
|
||||||
|
prefix = if sel, do: "", else: "-"
|
||||||
|
"#{prefix}#{call}:#{grid}"
|
||||||
|
end
|
||||||
|
|
||||||
|
defp encode_url_station(_), do: nil
|
||||||
|
|
||||||
defp toggle_station(%{assigns: %{persisted?: true}} = socket, id) do
|
defp toggle_station(%{assigns: %{persisted?: true}} = socket, id) do
|
||||||
user = current_user(socket)
|
user = current_user(socket)
|
||||||
|
|
||||||
|
|
@ -347,7 +433,7 @@ defmodule MicrowavepropWeb.RoverLive do
|
||||||
home: home
|
home: home
|
||||||
} = socket.assigns
|
} = socket.assigns
|
||||||
|
|
||||||
grid = Maidenhead.from_latlon(lat, lon, 6)
|
grid = Maidenhead.from_latlon(lat, lon, 10)
|
||||||
elev_m = lookup_elev(lat, lon)
|
elev_m = lookup_elev(lat, lon)
|
||||||
dist_km = haversine_km(home, %{lat: lat, lon: lon})
|
dist_km = haversine_km(home, %{lat: lat, lon: lon})
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue