prop/lib/microwaveprop/rover/hilltop.ex

55 lines
1.6 KiB
Elixir

defmodule Microwaveprop.Rover.Hilltop do
@moduledoc """
Snaps a propagation-grid cell center to the highest SRTM point inside
its footprint. The propagation grid is on a 0.125° lattice (~14 km),
but a rover wants to park on the actual hilltop inside that cell, not
at the nominal cell center. Sample SRTM at sub-cell resolution and
return the highest point.
"""
alias Microwaveprop.Rover.Elevation
@default_half_deg 0.0625
@default_step_deg 0.01
@type latlon :: {float(), float()}
@doc """
Returns `{best_lat, best_lon, best_elev}` for the highest SRTM point
inside `±half_deg` of `cell_center`, or `nil` if SRTM coverage is
missing across the whole footprint.
"""
@spec snap(latlon(), keyword()) :: {float(), float(), integer()} | nil
def snap({lat, lon}, opts \\ []) do
half = Keyword.get(opts, :half_deg, @default_half_deg)
step = Keyword.get(opts, :step_deg, @default_step_deg)
elev_lookup = Keyword.get(opts, :elev_lookup, &Elevation.lookup_many/1)
points = sample_grid(lat, lon, half, step)
elev_map = elev_lookup.(points)
points
|> Enum.flat_map(fn pt ->
case Map.get(elev_map, pt) do
nil -> []
elev -> [{pt, elev}]
end
end)
|> case do
[] ->
nil
list ->
{{best_lat, best_lon}, best_elev} = Enum.max_by(list, fn {_, e} -> e end)
{best_lat, best_lon, best_elev}
end
end
defp sample_grid(lat, lon, half, step) do
n = max(trunc(half / step), 1)
for i <- -n..n, j <- -n..n do
{Float.round(lat + i * step, 4), Float.round(lon + j * step, 4)}
end
end
end