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