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.
180 lines
5.6 KiB
Elixir
180 lines
5.6 KiB
Elixir
defmodule Microwaveprop.Terrain.SrtmTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Microwaveprop.Terrain.Srtm
|
|
|
|
@tiles_dir Path.expand("~/srtm/tiles")
|
|
@has_tiles File.exists?(Path.join(@tiles_dir, "N32W097.hgt"))
|
|
|
|
# A full SRTM tile: 3601 * 3601 samples, each 2 bytes big-endian signed
|
|
@samples 3601
|
|
@test_elevation 150
|
|
@synthetic_hgt :binary.copy(<<@test_elevation::signed-big-integer-size(16)>>, @samples * @samples)
|
|
|
|
describe "tile_filename/2" do
|
|
test "northern hemisphere, western hemisphere" do
|
|
assert Srtm.tile_filename(32.78, -96.8) == "N32W097.hgt"
|
|
end
|
|
|
|
test "southern hemisphere, eastern hemisphere" do
|
|
assert Srtm.tile_filename(-0.5, 10.3) == "S01E010.hgt"
|
|
end
|
|
|
|
test "equator, prime meridian boundary" do
|
|
assert Srtm.tile_filename(0.5, -0.5) == "N00W001.hgt"
|
|
end
|
|
|
|
test "exact integer coordinates" do
|
|
assert Srtm.tile_filename(32.0, -97.0) == "N32W097.hgt"
|
|
end
|
|
|
|
test "negative lon floor wrapping" do
|
|
assert Srtm.tile_filename(35.2, -106.7) == "N35W107.hgt"
|
|
end
|
|
end
|
|
|
|
describe "download_tile/3" do
|
|
test "downloads and decompresses tile on success" do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
on_exit(fn -> File.rm_rf!(tmp_dir) end)
|
|
|
|
gzipped = :zlib.gzip(@synthetic_hgt)
|
|
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 200, gzipped)
|
|
end)
|
|
|
|
assert {:ok, path} = Srtm.download_tile(32.0, -97.0, tmp_dir)
|
|
assert path == Path.join(tmp_dir, "N32W097.hgt")
|
|
assert File.exists?(path)
|
|
assert File.read!(path) == @synthetic_hgt
|
|
end
|
|
|
|
test "returns {:error, :not_available} on 404" do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
on_exit(fn -> File.rm_rf!(tmp_dir) end)
|
|
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 404, "Not Found")
|
|
end)
|
|
|
|
assert {:error, :not_available} = Srtm.download_tile(80.0, 0.0, tmp_dir)
|
|
end
|
|
|
|
test "returns error message on other HTTP status" do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
on_exit(fn -> File.rm_rf!(tmp_dir) end)
|
|
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 500, "Internal Server Error")
|
|
end)
|
|
|
|
assert {:error, "SRTM download HTTP 500"} = Srtm.download_tile(32.0, -97.0, tmp_dir)
|
|
end
|
|
end
|
|
|
|
describe "lookup/3 with auto-download" do
|
|
test "auto-downloads missing tile and returns elevation" do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
on_exit(fn -> File.rm_rf!(tmp_dir) end)
|
|
|
|
gzipped = :zlib.gzip(@synthetic_hgt)
|
|
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 200, gzipped)
|
|
end)
|
|
|
|
assert {:ok, @test_elevation} = Srtm.lookup(32.0, -97.0, tmp_dir)
|
|
end
|
|
|
|
test "returns {:error, :no_tile} when download fails" do
|
|
tmp_dir = Path.join(System.tmp_dir!(), "srtm_test_#{System.unique_integer([:positive])}")
|
|
File.mkdir_p!(tmp_dir)
|
|
|
|
on_exit(fn -> File.rm_rf!(tmp_dir) end)
|
|
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 404, "Not Found")
|
|
end)
|
|
|
|
assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, tmp_dir)
|
|
end
|
|
end
|
|
|
|
describe "lookup/3" do
|
|
@describetag :srtm
|
|
|
|
@tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
|
|
test "returns elevation for Dallas area" do
|
|
assert {:ok, elev} = Srtm.lookup(32.78, -96.8, @tiles_dir)
|
|
# Dallas is roughly 120-200m elevation
|
|
assert is_number(elev)
|
|
assert elev > 100
|
|
assert elev < 300
|
|
end
|
|
|
|
@tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
|
|
test "returns {:error, :no_tile} for missing tile" do
|
|
Req.Test.stub(Srtm, fn conn ->
|
|
Plug.Conn.send_resp(conn, 404, "Not Found")
|
|
end)
|
|
|
|
assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, @tiles_dir)
|
|
end
|
|
end
|
|
|
|
describe "fetch_elevation_profile/6" do
|
|
@describetag :srtm
|
|
|
|
@tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
|
|
test "returns profile with correct number of points" do
|
|
# Dallas to a point within the same tile
|
|
assert {:ok, profile} =
|
|
Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 10)
|
|
|
|
assert length(profile) == 11
|
|
|
|
first = hd(profile)
|
|
assert Map.has_key?(first, :lat)
|
|
assert Map.has_key?(first, :lon)
|
|
assert Map.has_key?(first, :d)
|
|
assert Map.has_key?(first, :elev)
|
|
assert Map.has_key?(first, :dist_km)
|
|
end
|
|
|
|
@tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
|
|
test "all elevations are positive for Dallas area path" do
|
|
assert {:ok, profile} =
|
|
Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 8)
|
|
|
|
Enum.each(profile, fn pt ->
|
|
assert pt.elev > 0, "Expected positive elevation, got #{pt.elev}"
|
|
end)
|
|
end
|
|
|
|
@tag skip: !@has_tiles && "SRTM tiles not available at #{@tiles_dir}"
|
|
test "first and last dist_km are correct" do
|
|
assert {:ok, profile} =
|
|
Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, @tiles_dir, 4)
|
|
|
|
first = hd(profile)
|
|
last = List.last(profile)
|
|
|
|
assert_in_delta first.dist_km, 0.0, 0.01
|
|
assert last.dist_km > 0
|
|
end
|
|
|
|
test "returns error when tiles dir does not exist" do
|
|
assert {:error, _} =
|
|
Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, "/nonexistent", 4)
|
|
end
|
|
end
|
|
end
|