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 """
|
||||
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).
|
||||
"""
|
||||
@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
|
||||
# `90.0` / `180.0` would otherwise overflow the trunc into 'S'
|
||||
# (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>>
|
||||
|
||||
cond do
|
||||
precision == 4 ->
|
||||
grid
|
||||
if precision == 4 do
|
||||
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 ->
|
||||
# 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>>
|
||||
append_extended(grid, lat, lon, precision)
|
||||
end
|
||||
end
|
||||
|
||||
if precision == 8 do
|
||||
# Extended square (0-9), (5/60)/10 lon, (2.5/60)/10 lat
|
||||
e1 = trunc(lon * 60.0 / 0.5)
|
||||
e2 = trunc(lat * 60.0 / 0.25)
|
||||
grid <> <<?0 + e1, ?0 + e2>>
|
||||
else
|
||||
grid
|
||||
end
|
||||
defp append_extended(grid, _lat, _lon, 6), do: grid
|
||||
|
||||
defp append_extended(grid, lat, lon, precision) when precision in [8, 10] do
|
||||
# Extended square (0-9), (5'/60)/10 lon, (2.5'/60)/10 lat
|
||||
e1 = trunc(lon * 60.0 * 10.0 / 5.0)
|
||||
e2 = trunc(lat * 60.0 * 10.0 / 2.5)
|
||||
lon = lon - e1 * (5.0 / 60.0 / 10.0)
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ defmodule Microwaveprop.Rover.Compute do
|
|||
defp candidate_payload(cell, home) do
|
||||
drive_min = DriveTime.drive_min(cell.distance_km)
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -59,10 +59,65 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
top_candidates: [],
|
||||
station_form_error: nil,
|
||||
home_form_error: nil,
|
||||
scoring_loading: false
|
||||
scoring_loading: false,
|
||||
url_loaded?: false
|
||||
)}
|
||||
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 ────────────────────────────────────────────────
|
||||
|
||||
defp load_stations(socket) do
|
||||
|
|
@ -104,7 +159,7 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
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
|
||||
|
||||
|
|
@ -146,7 +201,10 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
case create_station(socket, attrs) do
|
||||
{: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} ->
|
||||
{:noreply, assign(socket, station_form_error: msg)}
|
||||
|
|
@ -206,11 +264,39 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
|
||||
defp handle_station_mutation(socket, id, fun) do
|
||||
case fun.(socket, id) do
|
||||
{:ok, stations} -> {:noreply, assign(socket, fixed_stations: stations)}
|
||||
{:error, _} -> {:noreply, socket}
|
||||
{:ok, stations} ->
|
||||
{:noreply, socket |> assign(fixed_stations: stations) |> sync_url()}
|
||||
|
||||
{:error, _} ->
|
||||
{:noreply, socket}
|
||||
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
|
||||
user = current_user(socket)
|
||||
|
||||
|
|
@ -347,7 +433,7 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
home: home
|
||||
} = socket.assigns
|
||||
|
||||
grid = Maidenhead.from_latlon(lat, lon, 6)
|
||||
grid = Maidenhead.from_latlon(lat, lon, 10)
|
||||
elev_m = lookup_elev(lat, lon)
|
||||
dist_km = haversine_km(home, %{lat: lat, lon: lon})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue