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.
163 lines
4.9 KiB
Elixir
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
|