feat(rover): replace max-drive-time slider with max-distance in miles

This commit is contained in:
Graham McIntire 2026-04-25 17:21:52 -05:00
parent 9b81e776ca
commit 915cd1f5a0
No known key found for this signature in database
GPG key ID: F4ABF488E6029E59
5 changed files with 34 additions and 37 deletions

View file

@ -7,14 +7,11 @@ interface RoverSliderHook extends ViewHook {
inputHandler: () => void inputHandler: () => void
} }
const AVG_SPEED_KMH = 65.0 const KM_PER_MI = 1.609344
const formatters: Record<string, (v: number) => string> = { const formatters: Record<string, (v: number) => string> = {
forecast: (v) => `+${v}h`, forecast: (v) => `+${v}h`,
drive: (v) => { distance: (v) => `${v} mi`
const hours = Math.round((v / 60) * 10) / 10
return `${hours}h`
}
} }
export const RoverSlider: Partial<RoverSliderHook> = { export const RoverSlider: Partial<RoverSliderHook> = {
@ -29,8 +26,8 @@ export const RoverSlider: Partial<RoverSliderHook> = {
const value = Number((this.el as HTMLInputElement).value) const value = Number((this.el as HTMLInputElement).value)
if (this.labelEl) this.labelEl.textContent = this.formatter(value) if (this.labelEl) this.labelEl.textContent = this.formatter(value)
if (this.format === "drive") { if (this.format === "distance") {
const km = (value * AVG_SPEED_KMH) / 60.0 const km = value * KM_PER_MI
window.dispatchEvent(new CustomEvent("rover:drive-radius", { detail: { km } })) window.dispatchEvent(new CustomEvent("rover:drive-radius", { detail: { km } }))
} }
} }

View file

@ -34,7 +34,7 @@ defmodule Microwaveprop.Rover.Compute do
band_mhz: non_neg_integer(), band_mhz: non_neg_integer(),
valid_time: DateTime.t(), valid_time: DateTime.t(),
mode: atom(), mode: atom(),
max_drive_min: integer(), max_distance_km: float(),
min_elev_gain: integer() min_elev_gain: integer()
} }
@ -53,12 +53,12 @@ defmodule Microwaveprop.Rover.Compute do
band_mhz: band_mhz, band_mhz: band_mhz,
valid_time: valid_time, valid_time: valid_time,
mode: mode, mode: mode,
max_drive_min: max_drive_min, max_distance_km: max_distance_km,
min_elev_gain: min_elev_gain min_elev_gain: min_elev_gain
} = args } = args
selected_stations = Enum.filter(stations, & &1.selected) selected_stations = Enum.filter(stations, & &1.selected)
radius_km = max_drive_min * @avg_speed_kmh / 60.0 radius_km = max_distance_km * 1.0
bbox = bbox_around(home, radius_km) bbox = bbox_around(home, radius_km)
raw_cells = scores_at.(band_mhz, valid_time, bbox) raw_cells = scores_at.(band_mhz, valid_time, bbox)

View file

@ -31,7 +31,8 @@ defmodule MicrowavepropWeb.RoverLive do
@default_band 10_000 @default_band 10_000
@default_mode :ssb @default_mode :ssb
@default_forecast_hour 0 @default_forecast_hour 0
@default_max_drive_min 120 @default_max_distance_mi 80
@km_per_mi 1.609344
@avg_speed_kmh 65.0 @avg_speed_kmh 65.0
# NTMS-area anonymous home: EM13 centroid. # NTMS-area anonymous home: EM13 centroid.
@ -51,8 +52,8 @@ defmodule MicrowavepropWeb.RoverLive do
band: @default_band, band: @default_band,
mode: @default_mode, mode: @default_mode,
forecast_hour: @default_forecast_hour, forecast_hour: @default_forecast_hour,
max_drive_min: @default_max_drive_min, max_distance_mi: @default_max_distance_mi,
drive_radius_km: drive_radius_km(@default_max_drive_min), drive_radius_km: drive_radius_km(@default_max_distance_mi),
fixed_stations: fixed_stations, fixed_stations: fixed_stations,
persisted?: persisted?, persisted?: persisted?,
home: home, home: home,
@ -163,7 +164,7 @@ defmodule MicrowavepropWeb.RoverLive do
defp home_label(lat, lon), do: Maidenhead.from_latlon(lat, lon, 10) 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_distance_mi), do: max_distance_mi * @km_per_mi
# ── Events ──────────────────────────────────────────────────────────── # ── Events ────────────────────────────────────────────────────────────
@ -178,13 +179,13 @@ defmodule MicrowavepropWeb.RoverLive do
{:noreply, assign(socket, forecast_hour: hour)} {:noreply, assign(socket, forecast_hour: hour)}
end end
def handle_event("set_max_drive_time", %{"value" => v}, socket) do def handle_event("set_max_distance", %{"value" => v}, socket) do
minutes = v |> parse_int(@default_max_drive_min) |> clamp(30, 240) miles = v |> parse_int(@default_max_distance_mi) |> clamp(25, 250)
radius_km = drive_radius_km(minutes) radius_km = drive_radius_km(miles)
{:noreply, {:noreply,
socket socket
|> assign(max_drive_min: minutes, drive_radius_km: radius_km) |> assign(max_distance_mi: miles, drive_radius_km: radius_km)
|> push_event("update_drive_radius", %{km: radius_km})} |> push_event("update_drive_radius", %{km: radius_km})}
end end
@ -666,7 +667,7 @@ defmodule MicrowavepropWeb.RoverLive do
band_mhz: socket.assigns.band, band_mhz: socket.assigns.band,
valid_time: socket.assigns.current_valid_time || DateTime.utc_now(), valid_time: socket.assigns.current_valid_time || DateTime.utc_now(),
mode: socket.assigns.mode, mode: socket.assigns.mode,
max_drive_min: socket.assigns.max_drive_min, max_distance_km: socket.assigns.drive_radius_km,
min_elev_gain: 0 min_elev_gain: 0
} }
end end
@ -916,26 +917,25 @@ defmodule MicrowavepropWeb.RoverLive do
</div> </div>
</.sidebar_section> </.sidebar_section>
<.sidebar_section title="MAX DRIVE TIME"> <.sidebar_section title="MAX DISTANCE">
<input <input
type="range" type="range"
min="30" min="25"
max="240" max="250"
step="15" step="25"
value={@max_drive_min} value={@max_distance_mi}
phx-change="set_max_drive_time" phx-change="set_max_distance"
phx-debounce="200" phx-debounce="200"
phx-hook="RoverSlider" phx-hook="RoverSlider"
phx-update="ignore" phx-update="ignore"
id="rover-drive-slider" id="rover-distance-slider"
data-label-id="rover-drive-label" data-label-id="rover-distance-label"
data-format="drive" data-format="distance"
name="value" name="value"
class="range range-xs range-primary" class="range range-xs range-primary"
/> />
<div class="text-xs opacity-70 mt-1"> <div class="text-xs opacity-70 mt-1">
<span id="rover-drive-label">{Float.round(@max_drive_min / 60, 1)}h</span> <span id="rover-distance-label">{@max_distance_mi} mi</span> from {@home.label}
from {@home.label}
</div> </div>
</.sidebar_section> </.sidebar_section>

View file

@ -28,7 +28,7 @@ defmodule Microwaveprop.Rover.ComputeTest do
band_mhz: 10_000, band_mhz: 10_000,
valid_time: ~U[2026-04-25 12:00:00Z], valid_time: ~U[2026-04-25 12:00:00Z],
mode: :ssb, mode: :ssb,
max_drive_min: 60, max_distance_km: 65.0,
min_elev_gain: 0 min_elev_gain: 0
} }
@ -87,7 +87,7 @@ defmodule Microwaveprop.Rover.ComputeTest do
band_mhz: 10_000, band_mhz: 10_000,
valid_time: ~U[2026-04-25 12:00:00Z], valid_time: ~U[2026-04-25 12:00:00Z],
mode: :ssb, mode: :ssb,
max_drive_min: 60, max_distance_km: 65.0,
min_elev_gain: 200 min_elev_gain: 200
}, },
scores_at: scores_at, scores_at: scores_at,

View file

@ -20,7 +20,7 @@ defmodule MicrowavepropWeb.RoverLiveTest do
assert html =~ "FIXED STATIONS" assert html =~ "FIXED STATIONS"
assert html =~ "FORECAST HOUR" assert html =~ "FORECAST HOUR"
assert html =~ "BAND" assert html =~ "BAND"
assert html =~ "MAX DRIVE TIME" assert html =~ "MAX DISTANCE"
refute html =~ ">MODE<" refute html =~ ">MODE<"
refute html =~ "MIN ELEV GAIN" refute html =~ "MIN ELEV GAIN"
end end
@ -63,14 +63,14 @@ defmodule MicrowavepropWeb.RoverLiveTest do
refute_push_event(view, "rover_results", %{}) refute_push_event(view, "rover_results", %{})
end end
test "set_max_drive_time updates the dashed circle radius", %{conn: conn} do test "set_max_distance updates the dashed circle radius", %{conn: conn} do
{:ok, view, _html} = live(conn, ~p"/rover") {:ok, view, _html} = live(conn, ~p"/rover")
render_hook(view, "set_max_drive_time", %{"value" => "120"}) render_hook(view, "set_max_distance", %{"value" => "100"})
# 120 min @ 65 km/h = 130 km # 100 mi = 160.93 km
assert_push_event(view, "update_drive_radius", %{km: km}) assert_push_event(view, "update_drive_radius", %{km: km})
assert_in_delta km, 130.0, 0.1 assert_in_delta km, 160.93, 0.1
end end
test "calculate triggers a rover_results push_event with cells + drive_radius_km", %{conn: conn} do test "calculate triggers a rover_results push_event with cells + drive_radius_km", %{conn: conn} do