Smooth viewshed boundary to remove SRTM elevation spikes

Apply 5-point circular moving average to reach_km values before
rendering the boundary polygon. Eliminates sharp spikes caused by
isolated terrain anomalies in SRTM data.
This commit is contained in:
Graham McIntire 2026-04-07 07:42:23 -05:00
parent 05fcb6f5be
commit a774e98350

View file

@ -129,6 +129,7 @@ defmodule Microwaveprop.Terrain.Viewshed do
{:exit, _} -> nil
end)
|> Enum.reject(&is_nil/1)
|> smooth_boundary(lat, lon)
%{origin: %{lat: lat, lon: lon}, boundary: boundary}
end
@ -180,6 +181,34 @@ defmodule Microwaveprop.Terrain.Viewshed do
Application.get_env(:microwaveprop, :srtm_tiles_dir, Path.expand("~/srtm/tiles"))
end
# Smooth reach_km values with a circular moving average to remove spikes
# from SRTM elevation artifacts, then recompute boundary lat/lon.
defp smooth_boundary(points, _origin_lat, _origin_lon) when length(points) < 5, do: points
defp smooth_boundary(points, origin_lat, origin_lon) do
reaches = Enum.map(points, & &1.reach_km)
n = length(reaches)
arr = :array.from_list(reaches)
half = 2
smoothed_reaches =
Enum.map(0..(n - 1), fn i ->
window =
for offset <- -half..half do
:array.get(rem(i + offset + n, n), arr)
end
Enum.sum(window) / length(window)
end)
points
|> Enum.zip(smoothed_reaches)
|> Enum.map(fn {pt, reach} ->
{lat, lon} = destination_point(origin_lat, origin_lon, pt.bearing, reach)
%{pt | reach_km: reach, lat: lat, lon: lon}
end)
end
defp find_first_obstructed_index(interior) do
Enum.find_index(interior, & &1.obstructed)
end