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