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
}
const AVG_SPEED_KMH = 65.0
const KM_PER_MI = 1.609344
const formatters: Record<string, (v: number) => string> = {
forecast: (v) => `+${v}h`,
drive: (v) => {
const hours = Math.round((v / 60) * 10) / 10
return `${hours}h`
}
distance: (v) => `${v} mi`
}
export const RoverSlider: Partial<RoverSliderHook> = {
@ -29,8 +26,8 @@ export const RoverSlider: Partial<RoverSliderHook> = {
const value = Number((this.el as HTMLInputElement).value)
if (this.labelEl) this.labelEl.textContent = this.formatter(value)
if (this.format === "drive") {
const km = (value * AVG_SPEED_KMH) / 60.0
if (this.format === "distance") {
const km = value * KM_PER_MI
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(),
valid_time: DateTime.t(),
mode: atom(),
max_drive_min: integer(),
max_distance_km: float(),
min_elev_gain: integer()
}
@ -53,12 +53,12 @@ defmodule Microwaveprop.Rover.Compute do
band_mhz: band_mhz,
valid_time: valid_time,
mode: mode,
max_drive_min: max_drive_min,
max_distance_km: max_distance_km,
min_elev_gain: min_elev_gain
} = args
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)
raw_cells = scores_at.(band_mhz, valid_time, bbox)

View file

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

View file

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

View file

@ -20,7 +20,7 @@ defmodule MicrowavepropWeb.RoverLiveTest do
assert html =~ "FIXED STATIONS"
assert html =~ "FORECAST HOUR"
assert html =~ "BAND"
assert html =~ "MAX DRIVE TIME"
assert html =~ "MAX DISTANCE"
refute html =~ ">MODE<"
refute html =~ "MIN ELEV GAIN"
end
@ -63,14 +63,14 @@ defmodule MicrowavepropWeb.RoverLiveTest do
refute_push_event(view, "rover_results", %{})
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")
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_in_delta km, 130.0, 0.1
assert_in_delta km, 160.93, 0.1
end
test "calculate triggers a rover_results push_event with cells + drive_radius_km", %{conn: conn} do