Add grid toggle, use 0.25deg resolution for coverage scoring
- Toggle button for Maidenhead grid overlay (on by default) - Coverage candidates now at 0.25° resolution (~28 km) instead of 2°×1° Maidenhead grids, matching HRRR propagation grid density - Coverage rectangles render as 0.25° cells for finer detail - Grid overlay is separate from coverage coloring
This commit is contained in:
parent
79aca95bc1
commit
caa03fed53
3 changed files with 62 additions and 22 deletions
|
|
@ -19,6 +19,9 @@ export const RoverMap = {
|
|||
this.stationMarkers.addTo(map)
|
||||
this.coverageLayer.addTo(map)
|
||||
|
||||
this.gridLayer = L.layerGroup().addTo(map)
|
||||
this.drawGridOverlay()
|
||||
|
||||
this.handleEvent("stations_updated", ({ stations }) => {
|
||||
this.stations = stations
|
||||
this.renderStations()
|
||||
|
|
@ -27,6 +30,14 @@ export const RoverMap = {
|
|||
this.handleEvent("coverage_updated", ({ grids }) => {
|
||||
this.renderCoverage(grids)
|
||||
})
|
||||
|
||||
this.handleEvent("toggle_grids", ({ show }) => {
|
||||
if (show) {
|
||||
this.gridLayer.addTo(this.map)
|
||||
} else {
|
||||
this.map.removeLayer(this.gridLayer)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
renderStations() {
|
||||
|
|
@ -64,10 +75,9 @@ export const RoverMap = {
|
|||
const maxScore = Math.max(...grids.map(g => g.coverage_score))
|
||||
|
||||
for (const g of grids) {
|
||||
// 4-char Maidenhead: 2° lon × 1° lat
|
||||
// Compute grid square bounds from the grid letters
|
||||
const bounds = this.gridBounds(g.grid)
|
||||
if (!bounds) continue
|
||||
// Render as 0.25° cells centered on the candidate point
|
||||
const half = 0.125
|
||||
const bounds = [[g.lat - half, g.lon - half], [g.lat + half, g.lon + half]]
|
||||
|
||||
const normalized = maxScore > 0 ? g.coverage_score / maxScore : 0
|
||||
const color = this.coverageColor(normalized)
|
||||
|
|
@ -149,6 +159,19 @@ export const RoverMap = {
|
|||
return "#ff4f4f"
|
||||
},
|
||||
|
||||
drawGridOverlay() {
|
||||
for (let lat = 25; lat <= 50; lat += 1) {
|
||||
L.polyline([[lat, -125], [lat, -66]], {
|
||||
color: "#888", weight: 0.5, opacity: 0.3
|
||||
}).addTo(this.gridLayer)
|
||||
}
|
||||
for (let lon = -125; lon <= -66; lon += 2) {
|
||||
L.polyline([[25, lon], [50, lon]], {
|
||||
color: "#888", weight: 0.5, opacity: 0.3
|
||||
}).addTo(this.gridLayer)
|
||||
}
|
||||
},
|
||||
|
||||
fitBounds() {
|
||||
const points = this.stations.map(s => [s.lat, s.lon])
|
||||
if (points.length >= 2) {
|
||||
|
|
|
|||
|
|
@ -79,20 +79,21 @@ defmodule Microwaveprop.Rover.Coverage do
|
|||
min_lon = max(Enum.min(lons) - range_deg, -125.0)
|
||||
max_lon = min(Enum.max(lons) + range_deg, -66.0)
|
||||
|
||||
# Generate at 4-char Maidenhead resolution (1° lat × 2° lon)
|
||||
lat_range = Enum.to_list(trunc(min_lat)..trunc(max_lat))
|
||||
lon_range = Enum.to_list(trunc(min_lon)..trunc(max_lon)//2)
|
||||
# Generate candidates at 0.25° resolution (between HRRR 0.125° and Maidenhead 1-2°)
|
||||
# This gives ~16x more candidates than Maidenhead but stays manageable
|
||||
step = 0.25
|
||||
|
||||
# Use center of grid square
|
||||
for_result =
|
||||
for lat <- lat_range, lon <- lon_range do
|
||||
clat = lat + 0.5
|
||||
clon = lon + 1.0
|
||||
grid = Geo.latlon_to_grid4(clat, clon)
|
||||
{grid, clat, clon}
|
||||
end
|
||||
for lat <- float_range(min_lat, max_lat, step),
|
||||
lon <- float_range(min_lon, max_lon, step) do
|
||||
grid = Geo.latlon_to_grid4(lat, lon)
|
||||
{grid, Float.round(lat, 3), Float.round(lon, 3)}
|
||||
end
|
||||
end
|
||||
|
||||
Enum.uniq_by(for_result, fn {grid, _, _} -> grid end)
|
||||
defp float_range(min, max, step) do
|
||||
(Float.ceil(min / step) * step)
|
||||
|> Stream.iterate(&(&1 + step))
|
||||
|> Enum.take_while(&(&1 <= max))
|
||||
end
|
||||
|
||||
defp fast_score_grid(grid, lat, lon, stations, band_mhz, max_range) do
|
||||
|
|
|
|||
|
|
@ -32,7 +32,8 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
selected_grid: nil,
|
||||
resolving: false,
|
||||
computing: false,
|
||||
url_loaded: false
|
||||
url_loaded: false,
|
||||
show_grids: true
|
||||
)}
|
||||
end
|
||||
|
||||
|
|
@ -80,6 +81,11 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
{:noreply, push_url(socket)}
|
||||
end
|
||||
|
||||
def handle_event("toggle_grids", _params, socket) do
|
||||
show = !socket.assigns.show_grids
|
||||
{:noreply, socket |> assign(show_grids: show) |> push_event("toggle_grids", %{show: show})}
|
||||
end
|
||||
|
||||
def handle_event("select_band", %{"band" => band_str}, socket) do
|
||||
socket = assign(socket, band: String.to_integer(band_str), coverage: [], selected_grid: nil)
|
||||
{:noreply, push_url(socket)}
|
||||
|
|
@ -254,11 +260,21 @@ defmodule MicrowavepropWeb.RoverLive do
|
|||
<div class="absolute top-2 left-2 z-[1000] w-80 max-h-[calc(100vh-1rem)] overflow-y-auto bg-base-100/95 shadow-lg rounded-box border border-base-300 p-3 flex flex-col gap-3">
|
||||
<div class="font-bold text-sm">Rover Planner</div>
|
||||
|
||||
<select phx-change="select_band" name="band" class="select select-bordered select-sm w-full">
|
||||
<%= for {label, value} <- @band_options do %>
|
||||
<option value={value} selected={value == to_string(@band)}>{label}</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<div class="flex gap-2">
|
||||
<select phx-change="select_band" name="band" class="select select-bordered select-sm flex-1">
|
||||
<%= for {label, value} <- @band_options do %>
|
||||
<option value={value} selected={value == to_string(@band)}>{label}</option>
|
||||
<% end %>
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
phx-click="toggle_grids"
|
||||
class={["btn btn-sm btn-square", if(@show_grids, do: "btn-primary", else: "btn-ghost")]}
|
||||
title="Toggle grid overlay"
|
||||
>
|
||||
<.icon name="hero-squares-2x2" class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form phx-submit="add_station" class="flex gap-1">
|
||||
<input
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue