For each (rover cell, fixed station) pair, sample SRTM along the great-circle path and add a per-link dB bonus proportional to the rover's elevation above the highest intermediate terrain. Rover spots on hilltops with clear sight to multiple stations now rise to the top. Also vendor Leaflet's layers control PNGs and copy them under priv/static/assets/css/images/ on build so /rover stops 404'ing on layers-2x.png.
77 lines
2.4 KiB
Elixir
77 lines
2.4 KiB
Elixir
defmodule Microwaveprop.Rover.PathTerrain do
|
|
@moduledoc """
|
|
Per-link terrain clearance for the rover scoring pipeline.
|
|
|
|
For each (rover cell, fixed station) pair, samples elevation along the
|
|
great-circle path between them and reports how far the rover sits
|
|
above the highest intermediate terrain. Positive clearance means the
|
|
rover should clear trees/buildings between it and the station; negative
|
|
clearance means the path is blocked by an intervening ridge.
|
|
"""
|
|
|
|
alias Microwaveprop.Rover.Elevation
|
|
|
|
# Number of intermediate samples between rover and station.
|
|
@sample_count 12
|
|
|
|
@type latlon :: {float(), float()}
|
|
|
|
@doc """
|
|
Returns `%{ {rover_pt, station_pt} => clearance_m | nil }`.
|
|
|
|
`clearance_m` is `rover_elev - max(intermediate_path_elev)`. `nil` when
|
|
the rover cell or the entire intermediate path lacks SRTM coverage.
|
|
"""
|
|
@spec clearance_map([map()], [map()], keyword()) :: %{{latlon(), latlon()} => integer() | nil}
|
|
def clearance_map(cells, stations, opts \\ []) when is_list(cells) and is_list(stations) do
|
|
elev_lookup = Keyword.get(opts, :elev_lookup, &Elevation.lookup_many/1)
|
|
|
|
pairs =
|
|
for cell <- cells, station <- stations do
|
|
{{cell.lat, cell.lon}, {station.lat, station.lon}}
|
|
end
|
|
|
|
sample_points =
|
|
pairs
|
|
|> Enum.flat_map(fn {a, b} -> intermediate_samples(a, b) end)
|
|
|> Enum.uniq()
|
|
|
|
rover_points = Enum.map(cells, fn c -> {c.lat, c.lon} end)
|
|
elev = elev_lookup.(rover_points ++ sample_points)
|
|
|
|
Map.new(pairs, fn {rover_pt, station_pt} ->
|
|
{{rover_pt, station_pt}, clearance(rover_pt, station_pt, elev)}
|
|
end)
|
|
end
|
|
|
|
defp clearance(rover_pt, station_pt, elev) do
|
|
rover_elev = Map.get(elev, rover_pt)
|
|
|
|
case rover_elev do
|
|
nil ->
|
|
nil
|
|
|
|
_ ->
|
|
path_max =
|
|
rover_pt
|
|
|> intermediate_samples(station_pt)
|
|
|> Enum.map(&Map.get(elev, &1))
|
|
|> Enum.reject(&is_nil/1)
|
|
|> safe_max()
|
|
|
|
if path_max, do: rover_elev - path_max
|
|
end
|
|
end
|
|
|
|
defp intermediate_samples({lat1, lon1}, {lat2, lon2}) do
|
|
# Linear interpolation in lat/lon — fine at the distances we work with
|
|
# (≤200 mi). Skip endpoints (i=0 is the rover, i=N+1 is the station).
|
|
for i <- 1..@sample_count do
|
|
f = i / (@sample_count + 1)
|
|
{lat1 + f * (lat2 - lat1), lon1 + f * (lon2 - lon1)}
|
|
end
|
|
end
|
|
|
|
defp safe_max([]), do: nil
|
|
defp safe_max(xs), do: Enum.max(xs)
|
|
end
|