feat(rover): replace max-drive-time slider with max-distance in miles
This commit is contained in:
parent
9b81e776ca
commit
915cd1f5a0
5 changed files with 34 additions and 37 deletions
|
|
@ -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 } }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue