defmodule Microwaveprop.GeoPropertyTest do @moduledoc """ StreamData property tests for `Microwaveprop.Geo`. The haversine formula defines a metric on the surface of the Earth, so it must satisfy the three metric axioms: non-negativity with identity-of-indiscernibles, symmetry, and the triangle inequality. These properties encode those axioms directly, plus the global bound of half the Earth's circumference. """ use ExUnit.Case, async: true use ExUnitProperties alias Microwaveprop.Geo @half_circumference_km 20_015.1 describe "haversine_km/4 metric axioms" do property "distance from a point to itself is exactly 0" do check all( lat <- float(min: -89.9, max: 89.9), lon <- float(min: -179.9, max: 179.9) ) do assert Geo.haversine_km(lat, lon, lat, lon) == 0.0 end end property "is non-negative for every pair of points" do check all( lat1 <- float(min: -89.9, max: 89.9), lon1 <- float(min: -179.9, max: 179.9), lat2 <- float(min: -89.9, max: 89.9), lon2 <- float(min: -179.9, max: 179.9) ) do assert Geo.haversine_km(lat1, lon1, lat2, lon2) >= 0 end end property "is symmetric in its endpoints" do check all( lat1 <- float(min: -89.9, max: 89.9), lon1 <- float(min: -179.9, max: 179.9), lat2 <- float(min: -89.9, max: 89.9), lon2 <- float(min: -179.9, max: 179.9) ) do forward = Geo.haversine_km(lat1, lon1, lat2, lon2) reverse = Geo.haversine_km(lat2, lon2, lat1, lon1) assert_in_delta forward, reverse, 1.0e-9 end end property "satisfies the triangle inequality for any three points" do check all( lat1 <- float(min: -89.9, max: 89.9), lon1 <- float(min: -179.9, max: 179.9), lat2 <- float(min: -89.9, max: 89.9), lon2 <- float(min: -179.9, max: 179.9), lat3 <- float(min: -89.9, max: 89.9), lon3 <- float(min: -179.9, max: 179.9) ) do d_ab = Geo.haversine_km(lat1, lon1, lat2, lon2) d_bc = Geo.haversine_km(lat2, lon2, lat3, lon3) d_ac = Geo.haversine_km(lat1, lon1, lat3, lon3) # Small epsilon for accumulated floating-point error. assert d_ac <= d_ab + d_bc + 1.0e-6 end end property "is bounded above by half the Earth's circumference" do check all( lat1 <- float(min: -89.9, max: 89.9), lon1 <- float(min: -179.9, max: 179.9), lat2 <- float(min: -89.9, max: 89.9), lon2 <- float(min: -179.9, max: 179.9) ) do assert Geo.haversine_km(lat1, lon1, lat2, lon2) <= @half_circumference_km end end end describe "bearing_deg/4" do property "result is always in [0, 360) for distinct points" do check all( lat1 <- float(min: -80.0, max: 80.0), lon1 <- float(min: -179.0, max: 179.0), # Ensure a nonzero displacement. dlat <- float(min: 0.1, max: 5.0), dlon <- float(min: 0.1, max: 5.0) ) do b = Geo.bearing_deg(lat1, lon1, lat1 + dlat, lon1 + dlon) assert b >= 0.0 assert b < 360.0 end end end end