SRTM profiles no longer abort on missing tiles — water/ocean areas return 0m elevation instead of failing the entire profile and falling back to the rate-limited API. Elevation client and viewshed now pass download: true to auto-fetch missing tiles from S3. NFS mount changed to writable so downloaded tiles persist across pods.
200 lines
6.2 KiB
Elixir
200 lines
6.2 KiB
Elixir
defmodule Microwaveprop.Terrain.SrtmTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
import ExUnit.CaptureLog
|
|
|
|
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)
|
|
|
|
capture_log(fn ->
|
|
assert {:error, :not_available} = Srtm.download_tile(80.0, 0.0, tmp_dir)
|
|
end)
|
|
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)
|
|
|
|
capture_log(fn ->
|
|
assert {:error, "SRTM download HTTP 500"} = Srtm.download_tile(32.0, -97.0, tmp_dir)
|
|
end)
|
|
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, download: true)
|
|
end
|
|
|
|
test "returns {:error, :no_tile} without download opt" 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)
|
|
|
|
assert {:error, :no_tile} = 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)
|
|
|
|
capture_log(fn ->
|
|
assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, tmp_dir, download: true)
|
|
end)
|
|
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)
|
|
|
|
capture_log(fn ->
|
|
assert {:error, :no_tile} = Srtm.lookup(80.0, 0.0, @tiles_dir)
|
|
end)
|
|
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 zero elevation when tiles dir does not exist" do
|
|
assert {:ok, profile} =
|
|
Srtm.fetch_elevation_profile(32.78, -96.8, 32.5, -96.5, "/nonexistent", 4)
|
|
|
|
assert Enum.all?(profile, fn p -> p.elev == 0 end)
|
|
end
|
|
end
|
|
end
|