prop/test/microwaveprop/terrain/elevation_client_test.exs
Graham McIntire 2ae60d036b
Add SRTM auto-download on tile miss from AWS Terrain Tiles
When a local .hgt tile is missing, download it from the public AWS S3
skadi bucket, decompress with zlib, and write to the tiles directory
before retrying the lookup. Falls back to Open-Meteo/OpenTopo APIs if
the download fails.
2026-03-30 08:36:21 -05:00

163 lines
4.9 KiB
Elixir

defmodule Microwaveprop.Terrain.ElevationClientTest do
use ExUnit.Case, async: true
alias Microwaveprop.Terrain.ElevationClient
describe "sample_path/5" do
test "generates N+1 evenly spaced points" do
points = ElevationClient.sample_path(32.9, -97.0, 30.3, -97.7, 4)
assert length(points) == 5
first = hd(points)
last = List.last(points)
assert_in_delta first.lat, 32.9, 0.001
assert_in_delta first.lon, -97.0, 0.001
assert_in_delta first.d, 0.0, 0.001
assert_in_delta last.lat, 30.3, 0.001
assert_in_delta last.lon, -97.7, 0.001
assert_in_delta last.d, 1.0, 0.001
end
test "midpoint is correct" do
points = ElevationClient.sample_path(0.0, 0.0, 10.0, 10.0, 2)
mid = Enum.at(points, 1)
assert_in_delta mid.lat, 5.0, 0.001
assert_in_delta mid.lon, 5.0, 0.001
assert_in_delta mid.d, 0.5, 0.001
end
end
describe "fetch_elevation_profile/5" do
test "returns profile with elevations from Open-Meteo" do
Req.Test.stub(ElevationClient, fn conn ->
Req.Test.json(conn, %{"elevation" => [200.0, 250.0, 180.0]})
end)
assert {:ok, profile} =
ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2)
assert length(profile) == 3
first = hd(profile)
assert Map.has_key?(first, :lat)
assert Map.has_key?(first, :lon)
assert Map.has_key?(first, :elev)
assert Map.has_key?(first, :dist_km)
assert first.elev == 200.0
assert_in_delta first.dist_km, 0.0, 0.001
end
test "batches requests when more than 100 points" do
call_count = :counters.new(1, [:atomics])
Req.Test.stub(ElevationClient, fn conn ->
:counters.add(call_count, 1, 1)
# Return 101 elevations for first batch, or however many requested
params = Plug.Conn.fetch_query_params(conn).query_params
lat_count = params["latitude"] |> String.split(",") |> length()
elevations = List.duplicate(100.0, lat_count)
Req.Test.json(conn, %{"elevation" => elevations})
end)
assert {:ok, profile} =
ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 150)
# 151 points → 2 batches (100 + 51)
assert length(profile) == 151
assert :counters.get(call_count, 1) == 2
end
test "falls back to Open-Topo-Data on Open-Meteo failure" do
call_count = :counters.new(1, [:atomics])
Req.Test.stub(ElevationClient, fn conn ->
:counters.add(call_count, 1, 1)
current = :counters.get(call_count, 1)
if current == 1 do
# First call (Open-Meteo) fails
Plug.Conn.send_resp(conn, 500, "error")
else
# Second call (Open-Topo-Data) succeeds
Req.Test.json(conn, %{
"status" => "OK",
"results" => [
%{"elevation" => 100.0},
%{"elevation" => 150.0},
%{"elevation" => 120.0}
]
})
end
end)
assert {:ok, profile} =
ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2)
assert length(profile) == 3
assert hd(profile).elev == 100.0
end
test "returns error when both APIs fail" do
Req.Test.stub(ElevationClient, fn conn ->
Plug.Conn.send_resp(conn, 500, "error")
end)
assert {:error, _reason} =
ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2)
end
end
end
defmodule Microwaveprop.Terrain.ElevationClientSrtmTest do
use ExUnit.Case, async: false
alias Microwaveprop.Terrain.ElevationClient
@tiles_dir Path.expand("~/srtm/tiles")
@has_tiles File.exists?(Path.join(@tiles_dir, "N32W097.hgt"))
setup do
prev = Application.get_env(:microwaveprop, :srtm_tiles_dir)
on_exit(fn ->
if prev,
do: Application.put_env(:microwaveprop, :srtm_tiles_dir, prev),
else: Application.delete_env(:microwaveprop, :srtm_tiles_dir)
end)
:ok
end
describe "SRTM integration" do
@tag :srtm
@tag skip: !@has_tiles && "SRTM tiles not available"
test "uses local SRTM tiles when configured" do
Application.put_env(:microwaveprop, :srtm_tiles_dir, @tiles_dir)
assert {:ok, profile} =
ElevationClient.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, 4)
assert length(profile) == 5
assert hd(profile).elev > 0
end
@tag :srtm
test "falls back to API when SRTM tiles missing" do
Application.put_env(:microwaveprop, :srtm_tiles_dir, "/nonexistent/tiles")
Req.Test.stub(ElevationClient, fn conn ->
Req.Test.json(conn, %{"elevation" => [200.0, 250.0, 180.0]})
end)
assert {:ok, profile} =
ElevationClient.fetch_elevation_profile(32.9, -97.0, 30.3, -97.7, 2)
assert length(profile) == 3
assert hd(profile).elev == 200.0
end
end
end