|
|
""" |
|
|
Unit tests for the Haversine driving time calculator in Location class. |
|
|
|
|
|
These tests verify that the driving time calculations correctly implement |
|
|
the Haversine formula for great-circle distance on Earth. |
|
|
""" |
|
|
from vehicle_routing.domain import Location |
|
|
|
|
|
|
|
|
class TestHaversineDrivingTime: |
|
|
"""Tests for Location.driving_time_to() using Haversine formula.""" |
|
|
|
|
|
def test_same_location_returns_zero(self): |
|
|
"""Same location should return 0 driving time.""" |
|
|
loc = Location(latitude=40.0, longitude=-75.0) |
|
|
assert loc.driving_time_to(loc) == 0 |
|
|
|
|
|
def test_same_coordinates_returns_zero(self): |
|
|
"""Two locations with same coordinates should return 0.""" |
|
|
loc1 = Location(latitude=40.0, longitude=-75.0) |
|
|
loc2 = Location(latitude=40.0, longitude=-75.0) |
|
|
assert loc1.driving_time_to(loc2) == 0 |
|
|
|
|
|
def test_symmetric_distance(self): |
|
|
"""Distance from A to B should equal distance from B to A.""" |
|
|
loc1 = Location(latitude=0, longitude=0) |
|
|
loc2 = Location(latitude=3, longitude=4) |
|
|
assert loc1.driving_time_to(loc2) == loc2.driving_time_to(loc1) |
|
|
|
|
|
def test_equator_one_degree_longitude(self): |
|
|
""" |
|
|
One degree of longitude at the equator is approximately 111.32 km. |
|
|
At 50 km/h, this should take about 2.2 hours = 7920 seconds. |
|
|
""" |
|
|
loc1 = Location(latitude=0, longitude=0) |
|
|
loc2 = Location(latitude=0, longitude=1) |
|
|
driving_time = loc1.driving_time_to(loc2) |
|
|
|
|
|
assert 7500 < driving_time < 8500, f"Expected ~8000, got {driving_time}" |
|
|
|
|
|
def test_equator_one_degree_latitude(self): |
|
|
""" |
|
|
One degree of latitude is approximately 111.32 km everywhere. |
|
|
At 50 km/h, this should take about 2.2 hours = 7920 seconds. |
|
|
""" |
|
|
loc1 = Location(latitude=0, longitude=0) |
|
|
loc2 = Location(latitude=1, longitude=0) |
|
|
driving_time = loc1.driving_time_to(loc2) |
|
|
|
|
|
assert 7500 < driving_time < 8500, f"Expected ~8000, got {driving_time}" |
|
|
|
|
|
def test_realistic_us_cities(self): |
|
|
""" |
|
|
Test driving time between realistic US city coordinates. |
|
|
Philadelphia (39.95, -75.17) to New York (40.71, -74.01) |
|
|
Distance is approximately 130 km, should take ~2.6 hours at 50 km/h. |
|
|
""" |
|
|
philadelphia = Location(latitude=39.95, longitude=-75.17) |
|
|
new_york = Location(latitude=40.71, longitude=-74.01) |
|
|
driving_time = philadelphia.driving_time_to(new_york) |
|
|
|
|
|
|
|
|
assert 8500 < driving_time < 10500, f"Expected ~9400, got {driving_time}" |
|
|
|
|
|
def test_longer_distance(self): |
|
|
""" |
|
|
Test longer distance: Philadelphia to Hartford. |
|
|
Distance is approximately 290 km. |
|
|
""" |
|
|
philadelphia = Location(latitude=39.95, longitude=-75.17) |
|
|
hartford = Location(latitude=41.76, longitude=-72.68) |
|
|
driving_time = philadelphia.driving_time_to(hartford) |
|
|
|
|
|
|
|
|
assert 19000 < driving_time < 23000, f"Expected ~21000, got {driving_time}" |
|
|
|
|
|
def test_known_values_from_test_data(self): |
|
|
""" |
|
|
Verify the exact values used in constraint tests. |
|
|
These values are calculated using the Haversine formula. |
|
|
""" |
|
|
LOCATION_1 = Location(latitude=0, longitude=0) |
|
|
LOCATION_2 = Location(latitude=3, longitude=4) |
|
|
LOCATION_3 = Location(latitude=-1, longitude=1) |
|
|
|
|
|
|
|
|
assert LOCATION_1.driving_time_to(LOCATION_2) == 40018 |
|
|
assert LOCATION_2.driving_time_to(LOCATION_3) == 40025 |
|
|
assert LOCATION_1.driving_time_to(LOCATION_3) == 11322 |
|
|
|
|
|
def test_negative_coordinates(self): |
|
|
"""Test with negative latitude and longitude (Southern/Western hemisphere).""" |
|
|
loc1 = Location(latitude=-33.87, longitude=151.21) |
|
|
loc2 = Location(latitude=-37.81, longitude=144.96) |
|
|
driving_time = loc1.driving_time_to(loc2) |
|
|
|
|
|
|
|
|
assert 48000 < driving_time < 55000, f"Expected ~51400, got {driving_time}" |
|
|
|
|
|
def test_cross_hemisphere(self): |
|
|
"""Test crossing equator.""" |
|
|
loc1 = Location(latitude=10, longitude=0) |
|
|
loc2 = Location(latitude=-10, longitude=0) |
|
|
driving_time = loc1.driving_time_to(loc2) |
|
|
|
|
|
|
|
|
assert 155000 < driving_time < 165000, f"Expected ~160000, got {driving_time}" |
|
|
|
|
|
def test_cross_antimeridian(self): |
|
|
"""Test crossing the antimeridian (date line).""" |
|
|
loc1 = Location(latitude=0, longitude=179) |
|
|
loc2 = Location(latitude=0, longitude=-179) |
|
|
driving_time = loc1.driving_time_to(loc2) |
|
|
|
|
|
|
|
|
assert 15000 < driving_time < 17000, f"Expected ~16000, got {driving_time}" |
|
|
|
|
|
|
|
|
class TestHaversineInternalMethods: |
|
|
"""Tests for internal Haversine calculation methods.""" |
|
|
|
|
|
def test_to_cartesian_equator_prime_meridian(self): |
|
|
"""Test Cartesian conversion at equator/prime meridian intersection.""" |
|
|
loc = Location(latitude=0, longitude=0) |
|
|
x, y, z = loc._to_cartesian() |
|
|
|
|
|
assert abs(x - 0) < 0.001 |
|
|
assert abs(y - 0.5) < 0.001 |
|
|
assert abs(z - 0) < 0.001 |
|
|
|
|
|
def test_to_cartesian_north_pole(self): |
|
|
"""Test Cartesian conversion at North Pole.""" |
|
|
loc = Location(latitude=90, longitude=0) |
|
|
x, y, z = loc._to_cartesian() |
|
|
|
|
|
assert abs(x - 0) < 0.001 |
|
|
assert abs(y - 0) < 0.001 |
|
|
assert abs(z - 0.5) < 0.001 |
|
|
|
|
|
def test_meters_to_driving_seconds(self): |
|
|
"""Test conversion from meters to driving seconds.""" |
|
|
|
|
|
seconds = Location._meters_to_driving_seconds(50000) |
|
|
assert seconds == 3600 |
|
|
|
|
|
def test_meters_to_driving_seconds_zero(self): |
|
|
"""Zero meters should return zero seconds.""" |
|
|
assert Location._meters_to_driving_seconds(0) == 0 |
|
|
|
|
|
def test_meters_to_driving_seconds_small(self): |
|
|
"""Test small distances.""" |
|
|
|
|
|
seconds = Location._meters_to_driving_seconds(1000) |
|
|
assert seconds == 72 |
|
|
|
|
|
|
|
|
|