Fetch elevation data along the path between two stations via the Open-Meteo Elevation API (with Open-Topo-Data fallback), compute Fresnel zone clearance, earth bulge, and knife-edge diffraction loss, and store the results per QSO. - terrain_profiles table with migration and TerrainProfile schema - ElevationClient with batched API calls and fallback - TerrainAnalysis with Fresnel/diffraction physics (ITU-R P.526-15) - TerrainProfileWorker on Oban :terrain queue - QsoWeatherEnqueueWorker enqueues terrain jobs automatically - QSO show page displays verdict badge and collapsible elevation table - Reorder show page: terrain, soundings, solar, HRRR, surface obs - Fix Dockerfile wgrib2 build (add cmake dependency)
113 lines
3.5 KiB
Elixir
113 lines
3.5 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
|