Removed the subdir.
This commit is contained in:
173
pyrate/tests/plan/geometry/primitives/test_locations.py
Normal file
173
pyrate/tests/plan/geometry/primitives/test_locations.py
Normal file
@ -0,0 +1,173 @@
|
||||
"""Tests that the location classes in :mod:`pyrate.plan.geometry.location` work correctly."""
|
||||
|
||||
# Python standard math
|
||||
from math import isclose
|
||||
|
||||
# Typing
|
||||
from typing import cast
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Geometry
|
||||
from shapely.geometry import Point
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
from hypothesis import HealthCheck
|
||||
from hypothesis import settings
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import CartesianLocation
|
||||
from pyrate.plan.geometry import PolarLocation
|
||||
|
||||
# Test helpers
|
||||
from pyrate.common.testing.strategies.geometry import cartesian_locations
|
||||
from pyrate.common.testing.strategies.geometry import geo_bearings
|
||||
from pyrate.common.testing.strategies.geometry import polar_locations
|
||||
|
||||
# Local test helpers
|
||||
from . import is_near_special_point
|
||||
from . import simple_property_only_few_examples
|
||||
|
||||
|
||||
class TestLocationConversion(TestCase):
|
||||
"""Test for correct runtime behaviour in :mod:`pyrate.plan` location and shape primitives."""
|
||||
|
||||
@given(cartesian_locations(origin=polar_locations()))
|
||||
@settings(max_examples=20, suppress_health_check=(HealthCheck.data_too_large,)) # this is a slow test
|
||||
def test_projection_and_back_projection_origin_in_route(
|
||||
self, cartesian_location: CartesianLocation
|
||||
) -> None:
|
||||
"""Test the projection with an origin already being present in the geometry."""
|
||||
recreated = cartesian_location.to_polar().to_cartesian(cast(PolarLocation, cartesian_location.origin))
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
|
||||
@given(cartesian_locations(origin=st.none()), polar_locations())
|
||||
@simple_property_only_few_examples # this only checks very simple additional logic
|
||||
def test_projection_and_back_projection_origin_given_extra(
|
||||
self, cartesian_location: CartesianLocation, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with an origin being provided."""
|
||||
recreated = cartesian_location.to_polar(origin).to_cartesian(origin)
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
|
||||
@given(cartesian_locations(origin=st.none()))
|
||||
@simple_property_only_few_examples # this only checks very simple additional logic
|
||||
def test_projection_and_back_projection_origin_not_given(
|
||||
self, cartesian_location: CartesianLocation
|
||||
) -> None:
|
||||
"""Test the projection with no origin being given."""
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_location.to_polar()
|
||||
|
||||
@given(cartesian_locations(origin=polar_locations()), polar_locations())
|
||||
@simple_property_only_few_examples # this only checks very simple additional logic
|
||||
def test_projection_and_back_projection_origin_given_twice(
|
||||
self, cartesian_location: CartesianLocation, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with ambiguous origin being provided."""
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_location.to_polar(origin)
|
||||
|
||||
def test_distance_measuring_specific(self) -> None:
|
||||
"""Tests a specific input/output pair."""
|
||||
|
||||
location_a = PolarLocation(latitude=55.6544, longitude=139.74477)
|
||||
location_b = PolarLocation(latitude=21.4225, longitude=39.8261)
|
||||
distance = location_a.distance(location_b, approximate=False)
|
||||
self.assertAlmostEqual(distance, 8_665_850.116876071)
|
||||
|
||||
@given(
|
||||
polar_locations(),
|
||||
geo_bearings(),
|
||||
st.floats(min_value=1.0, max_value=100_000.0, allow_nan=False, allow_infinity=False),
|
||||
)
|
||||
def test_translation_is_invertible(
|
||||
self, original: PolarLocation, direction: float, distance: float
|
||||
) -> None:
|
||||
"""Tests that translation is invertible and a valid bearing is returned.
|
||||
|
||||
Warning:
|
||||
Only tests in-depth in the case where latitudes and longitudes are not near the poles.
|
||||
"""
|
||||
|
||||
# translate
|
||||
translated, back_direction = original.translate(direction, distance)
|
||||
self.assertGreaterEqual(back_direction, 0.0)
|
||||
self.assertLess(back_direction, 360.0)
|
||||
|
||||
# translate back
|
||||
translated_translated, back_back_direction = translated.translate(back_direction, distance)
|
||||
self.assertGreaterEqual(back_back_direction, 0.0)
|
||||
self.assertLess(back_back_direction, 360.0)
|
||||
|
||||
# the method seems to have problems at poles
|
||||
if not is_near_special_point(original) and not is_near_special_point(translated):
|
||||
# the method is rather rough, so we want to add larger tolerances than usual while checking
|
||||
self.assertTrue(isclose(direction, back_back_direction, abs_tol=1e-6))
|
||||
self.assertTrue(original.equals_exact(translated_translated, 1e-6))
|
||||
|
||||
@given(cartesian_locations())
|
||||
def test_from_shapely_conversion(self, cartesian_location: CartesianLocation) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.location.CartesianLocation.from_shapely` works."""
|
||||
|
||||
# we only want to compare the coordinates, so create a new instance without the identifier, name, etc.
|
||||
bare = CartesianLocation(cartesian_location.x, cartesian_location.y)
|
||||
bare_shapely = Point(cartesian_location.x, cartesian_location.y)
|
||||
recreated = CartesianLocation.from_shapely(bare_shapely)
|
||||
self.assertEqual(recreated, bare)
|
||||
|
||||
|
||||
class TestPolarLocationDistanceIsAMetric(TestCase):
|
||||
"""Makes sure that :meth:`~pyrate.plan.geometry.location.PolarLocation.distance` is a metric.
|
||||
|
||||
This should always succeed since we use a very stable external library for this.
|
||||
|
||||
See `Wikipedia <https://en.wikipedia.org/wiki/Metric_(mathematics)#Definition>`__ for the axioms.
|
||||
"""
|
||||
|
||||
@given(polar_locations(), polar_locations(), st.booleans())
|
||||
def test_distance_measuring_commutes_and_sanity_checks(
|
||||
self, location_a: PolarLocation, location_b: PolarLocation, approximate: bool
|
||||
) -> None:
|
||||
"""Assures flipping the sides when calculating distances does not make a significant difference."""
|
||||
|
||||
distance_1 = location_a.distance(location_b, approximate)
|
||||
distance_2 = location_b.distance(location_a, approximate)
|
||||
|
||||
# make sure it commutes
|
||||
self.assertAlmostEqual(distance_1, distance_2)
|
||||
|
||||
# make sure the distance is always positive
|
||||
self.assertGreaterEqual(distance_1, 0.0)
|
||||
self.assertGreaterEqual(distance_2, 0.0)
|
||||
|
||||
@given(polar_locations(), polar_locations(), polar_locations(), st.booleans())
|
||||
def test_distance_measuring_triangle_inequality(
|
||||
self,
|
||||
location_a: PolarLocation,
|
||||
location_b: PolarLocation,
|
||||
location_c: PolarLocation,
|
||||
approximate: bool,
|
||||
) -> None:
|
||||
"""Assures flipping the sides when calculating distances does not make a significant difference."""
|
||||
|
||||
distance_a_b = location_a.distance(location_b, approximate)
|
||||
distance_b_c = location_b.distance(location_c, approximate)
|
||||
distance_a_c = location_a.distance(location_c, approximate)
|
||||
|
||||
# allow for floating point errors
|
||||
abs_tolerance = 1e-6 # 1 micro meter
|
||||
self.assertGreaterEqual(distance_a_b + distance_b_c + abs_tolerance, distance_a_c)
|
||||
|
||||
@given(polar_locations(), st.booleans())
|
||||
def test_distance_measuring_to_itself_is_zero(self, location: PolarLocation, approximate: bool) -> None:
|
||||
"""Assures flipping the sides when calculating distances does not make a significant difference."""
|
||||
|
||||
distance = location.distance(location, approximate)
|
||||
|
||||
# make sure the distance is always positive and very close to zero
|
||||
self.assertGreaterEqual(distance, 0.0)
|
||||
self.assertAlmostEqual(distance, 0.0)
|
Reference in New Issue
Block a user