Removed the subdir.
This commit is contained in:
53
pyrate/tests/plan/geometry/primitives/__init__.py
Normal file
53
pyrate/tests/plan/geometry/primitives/__init__.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry` primitives for
|
||||
locations, polygons and trajectories.
|
||||
|
||||
Quite a few tests are marked with ``@settings(max_examples=<some small count>)`` since this test suite makes
|
||||
up a very large part of the total testing time and some tests just don't justify wasting many resources on
|
||||
them due to very simple code being tested.
|
||||
"""
|
||||
|
||||
# Python standard math
|
||||
from math import isclose
|
||||
|
||||
# Typing
|
||||
from typing import Union
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import HealthCheck
|
||||
from hypothesis import settings
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import PolarLocation
|
||||
from pyrate.plan.geometry import PolarPolygon
|
||||
from pyrate.plan.geometry import PolarRoute
|
||||
|
||||
|
||||
#: Tests that require the generation of cartesian routes are slow since the generation of examples is slow.
|
||||
#: As polar routes, cartesian polygons and polar polygons depend on this, they are also run at reduced rate.
|
||||
slow_route_max_examples = settings(
|
||||
max_examples=int(settings().max_examples * 0.1), suppress_health_check=(HealthCheck.too_slow,)
|
||||
)
|
||||
|
||||
|
||||
#: A test that only tests very few examples since the property to be tested is rather trivial and we do not
|
||||
#: want to invest significant amounts of time into it.
|
||||
simple_property_only_few_examples = settings(
|
||||
max_examples=int(max(5, settings().max_examples * 0.001)), suppress_health_check=(HealthCheck.too_slow,)
|
||||
)
|
||||
|
||||
|
||||
def is_near_special_point(polar_location: PolarLocation, tolerance: float = 1e-6) -> bool:
|
||||
"""Checks if the given ``polar_location`` is within ``tolerance`` of the poles or +/- 180° longitude."""
|
||||
return (
|
||||
isclose(polar_location.latitude, -90, abs_tol=tolerance)
|
||||
or isclose(polar_location.latitude, +90, abs_tol=tolerance)
|
||||
or isclose(polar_location.longitude, -180, abs_tol=tolerance)
|
||||
or isclose(polar_location.longitude, +180, abs_tol=tolerance)
|
||||
)
|
||||
|
||||
|
||||
def is_any_near_special_point(
|
||||
polar_line_object: Union[PolarPolygon, PolarRoute], tolerance: float = 1e-6
|
||||
) -> bool:
|
||||
"""Checks if any point in in the given geometry ``is_near_special_point`` within the ``tolerance``."""
|
||||
return any(is_near_special_point(location, tolerance) for location in polar_line_object.locations)
|
44
pyrate/tests/plan/geometry/primitives/test_common.py
Normal file
44
pyrate/tests/plan/geometry/primitives/test_common.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""Tests some general properties of geometries."""
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Scientific testing
|
||||
from numpy.testing import assert_array_almost_equal
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import CartesianGeometry
|
||||
|
||||
# Test helpers
|
||||
from pyrate.common.testing.strategies.geometry import cartesian_objects
|
||||
from pyrate.common.testing.strategies.geometry import geo_bearings
|
||||
|
||||
|
||||
class TestCartesianGeometries(TestCase):
|
||||
"""Asserts general properties of the cartesian geometries."""
|
||||
|
||||
@given(
|
||||
cartesian_objects(),
|
||||
geo_bearings(),
|
||||
st.floats(min_value=1.0, max_value=100_000.0),
|
||||
)
|
||||
def test_translation_is_invertible(
|
||||
self,
|
||||
original: CartesianGeometry,
|
||||
direction: float,
|
||||
distance: float,
|
||||
) -> None:
|
||||
"""Tests that translation is invertible and a valid backwards vector is returned."""
|
||||
|
||||
# translate & translate back
|
||||
translated, back_vector = original.translate(direction, distance)
|
||||
back_direction = (direction + 180) % 360
|
||||
translated_translated, back_back_vector = translated.translate(back_direction, distance)
|
||||
|
||||
# check the result
|
||||
assert_array_almost_equal(back_vector, -back_back_vector, decimal=9)
|
||||
self.assertTrue(original.equals_exact(translated_translated, tolerance=1e-9))
|
217
pyrate/tests/plan/geometry/primitives/test_geospatial.py
Normal file
217
pyrate/tests/plan/geometry/primitives/test_geospatial.py
Normal file
@ -0,0 +1,217 @@
|
||||
"""Tests that the geometry base classes in :mod:`pyrate.plan.geometry.geospatial` work correctly."""
|
||||
|
||||
# Python standard
|
||||
from copy import copy
|
||||
from copy import deepcopy
|
||||
from json import loads
|
||||
|
||||
# Typing
|
||||
from typing import Any
|
||||
from typing import Sequence
|
||||
from typing import Tuple
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
from hypothesis import HealthCheck
|
||||
from hypothesis import Phase
|
||||
from hypothesis import settings
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import CartesianLocation
|
||||
from pyrate.plan.geometry import CartesianPolygon
|
||||
from pyrate.plan.geometry import CartesianRoute
|
||||
from pyrate.plan.geometry import Direction
|
||||
from pyrate.plan.geometry import Geospatial
|
||||
from pyrate.plan.geometry import PolarLocation
|
||||
from pyrate.plan.geometry import PolarPolygon
|
||||
from pyrate.plan.geometry import PolarRoute
|
||||
|
||||
# Hypothesis testing
|
||||
from pyrate.common.testing.strategies.geometry import geospatial_objects
|
||||
|
||||
|
||||
_CARTESIAN_LOCATION_1 = CartesianLocation(5003.0, 139.231)
|
||||
_CARTESIAN_LOCATION_2 = CartesianLocation(600.1, 139.231)
|
||||
_POLAR_LOCATION_1 = PolarLocation(65.01, -180.0)
|
||||
_POLAR_LOCATION_2 = PolarLocation(-80.3, -180.0)
|
||||
|
||||
|
||||
class TestStringRepresentations(TestCase):
|
||||
"""Makes sure that the string conversion with ``__str__`` and ``__repr__`` works."""
|
||||
|
||||
_GROUND_TRUTH: Sequence[Tuple[Geospatial, str]] = [
|
||||
(
|
||||
_CARTESIAN_LOCATION_1,
|
||||
"CartesianLocation(east=5003.0, north=139.231)",
|
||||
),
|
||||
(
|
||||
PolarLocation(65.01, -180.0),
|
||||
"PolarLocation(latitude=65.00999999999999, longitude=-180.0)",
|
||||
),
|
||||
(
|
||||
CartesianPolygon([_CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_1]),
|
||||
"CartesianPolygon(locations=[(5003.0, 139.231), (5003.0, 139.231), (5003.0, 139.231), "
|
||||
"(5003.0, 139.231)])",
|
||||
),
|
||||
(
|
||||
PolarPolygon([_POLAR_LOCATION_1, _POLAR_LOCATION_1, _POLAR_LOCATION_1]),
|
||||
"PolarPolygon(locations=[PolarLocation(latitude=65.00999999999999, longitude=-180.0), "
|
||||
"PolarLocation(latitude=65.00999999999999, longitude=-180.0), "
|
||||
"PolarLocation(latitude=65.00999999999999, longitude=-180.0)])",
|
||||
),
|
||||
(
|
||||
CartesianRoute([_CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_2, _CARTESIAN_LOCATION_1]),
|
||||
"CartesianRoute(locations=[(5003.0, 139.231), (600.1, 139.231), (5003.0, 139.231)])",
|
||||
),
|
||||
(
|
||||
PolarRoute([_POLAR_LOCATION_1, _POLAR_LOCATION_2, _POLAR_LOCATION_1, _POLAR_LOCATION_1]),
|
||||
"PolarRoute(locations=[PolarLocation(latitude=65.00999999999999, longitude=-180.0), "
|
||||
"PolarLocation(latitude=-80.3, longitude=-180.0), "
|
||||
"PolarLocation(latitude=65.00999999999999, longitude=-180.0), "
|
||||
"PolarLocation(latitude=65.00999999999999, longitude=-180.0)])",
|
||||
),
|
||||
]
|
||||
|
||||
def test_conversions(self) -> None:
|
||||
"""Makes sure that all given geospatial objects can be converted."""
|
||||
|
||||
for geospatial, desired_str in TestStringRepresentations._GROUND_TRUTH:
|
||||
|
||||
with self.subTest(f"{type(geospatial)}.__str__"):
|
||||
self.assertEqual(str(geospatial), desired_str)
|
||||
|
||||
with self.subTest(f"{type(geospatial)}.__repr__"):
|
||||
self.assertEqual(repr(geospatial), desired_str)
|
||||
|
||||
|
||||
class TestGeoJsonRepresentations(TestCase):
|
||||
"""Makes sure that the conversion to GeoJSON via the common property ``__geo_interface__`` works."""
|
||||
|
||||
_GROUND_TRUTH: Sequence[Tuple[Geospatial, str]] = [
|
||||
(
|
||||
_CARTESIAN_LOCATION_1,
|
||||
'{"type": "Feature", "geometry": {"type": "Point", '
|
||||
'"coordinates": [5003.0, 139.231]}, "properties": {}}',
|
||||
),
|
||||
(
|
||||
PolarLocation(65.01, -180.0),
|
||||
'{"type": "Feature", "geometry": {"type": "Point", "coordinates": [-180.0, 65.01]}, '
|
||||
'"properties": {}}',
|
||||
),
|
||||
(
|
||||
CartesianPolygon([_CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_1]),
|
||||
'{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": '
|
||||
"[[[5003.0, 139.231], [5003.0, 139.231], [5003.0, 139.231], [5003.0, 139.231]]]}, "
|
||||
'"properties": {}}',
|
||||
),
|
||||
(
|
||||
PolarPolygon([_POLAR_LOCATION_1, _POLAR_LOCATION_1, _POLAR_LOCATION_1]),
|
||||
'{"type": "Feature", "geometry": {"type": "Polygon", '
|
||||
'"coordinates": [[[-180.0, 65.01], [-180.0, 65.01], [-180.0, 65.01]]]}, "properties": {}}',
|
||||
),
|
||||
(
|
||||
CartesianRoute([_CARTESIAN_LOCATION_1, _CARTESIAN_LOCATION_2, _CARTESIAN_LOCATION_1]),
|
||||
'{"type": "Feature", "geometry": {"type": "LineString", "coordinates": '
|
||||
'[[5003.0, 139.231], [600.1, 139.231], [5003.0, 139.231]]}, "properties": {}}',
|
||||
),
|
||||
(
|
||||
PolarRoute([_POLAR_LOCATION_1, _POLAR_LOCATION_2, _POLAR_LOCATION_1]),
|
||||
'{"type": "Feature", "geometry": {"type": "LineString", "coordinates": '
|
||||
'[[-180.0, 65.01], [-180.0, -80.3], [-180.0, 65.01]]}, "properties": {}}',
|
||||
),
|
||||
]
|
||||
|
||||
def test_conversions(self) -> None:
|
||||
"""Makes sure that all given geospatial objects can be converted."""
|
||||
|
||||
for geospatial, desired_geojson in TestGeoJsonRepresentations._GROUND_TRUTH:
|
||||
for indent in (None, 1, 8):
|
||||
with self.subTest(f"{type(geospatial)} with indent={indent}"):
|
||||
geojson = geospatial.to_geo_json(indent=indent)
|
||||
# load as JSON get get better error messages and become whitespace independent
|
||||
self.assertDictEqual(loads(geojson), loads(desired_geojson))
|
||||
|
||||
|
||||
class TestIdentifiers(TestCase):
|
||||
"""Makes sure that identifiers are validated correctly.
|
||||
|
||||
The test is only performed on polar locations for simplicity and because validation is handled in the
|
||||
abstract common parent class :class:`pyrate.plan.geometry.Geospatial` anyway.
|
||||
"""
|
||||
|
||||
@given(st.integers(min_value=0, max_value=(2**63) - 1))
|
||||
def test_on_locations_success(self, integer: int) -> None: # pylint: disable=no-self-use
|
||||
"""Tests that valid identifiers are accepted."""
|
||||
PolarLocation(latitude=0.0, longitude=0.0, identifier=integer)
|
||||
|
||||
@given(
|
||||
st.one_of(
|
||||
st.integers(max_value=-1), # negative numbers
|
||||
st.integers(min_value=2**63), # very large numbers
|
||||
)
|
||||
)
|
||||
def test_on_locations_rejected(self, integer: int) -> None:
|
||||
"""Tests that invalid identifiers are rejected."""
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarLocation(latitude=0.0, longitude=0.0, identifier=integer)
|
||||
|
||||
|
||||
class TestEqualityMethods(TestCase):
|
||||
"""Test the various equality methods."""
|
||||
|
||||
@given(geospatial_objects(stable=True))
|
||||
@settings(
|
||||
max_examples=200,
|
||||
suppress_health_check=(HealthCheck.data_too_large,),
|
||||
phases=(Phase.explicit, Phase.reuse, Phase.generate, Phase.target), # Do not shrink as it takes long
|
||||
)
|
||||
def test_equality_after_translation(self, geospatial: Any) -> None:
|
||||
"""Tests that translated objects are only equal under sufficient tolerance."""
|
||||
|
||||
# We discard the second output since it differs between cartesian and polar objects
|
||||
translated, _ = geospatial.translate(direction=Direction.North, distance=0.5)
|
||||
|
||||
# Try since generated primitives might cause an exception to be thrown
|
||||
# e.g. if projected routes become length 0
|
||||
try:
|
||||
# They should not be equal
|
||||
self.assertNotEqual(geospatial, translated)
|
||||
self.assertFalse(geospatial.equals(translated))
|
||||
self.assertFalse(geospatial.equals_exact(translated, tolerance=0.0))
|
||||
if hasattr(geospatial, "equals_almost_congruent"):
|
||||
self.assertFalse(
|
||||
geospatial.equals_almost_congruent(translated, abs_tolerance=0.0, rel_tolerance=0.0)
|
||||
)
|
||||
|
||||
# They should be equal within some tolerance (the tolerance needs to be large for the polar
|
||||
# variants)
|
||||
# TODO(Someone): re-enable; see #114
|
||||
# self.assertTrue(geospatial.equals_exact(translated, tolerance=5))
|
||||
# self.assertTrue(geospatial.almost_equals(translated, decimal=-1))
|
||||
|
||||
# We do not use `equals_almost_congruent` as it is not a per-coordinate difference and might cause
|
||||
# a very large symmetric difference on very large objects
|
||||
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
class TestCopyAndDeepcopy(TestCase):
|
||||
"""Tests that all geometric objects can be deep-copied."""
|
||||
|
||||
@given(geospatial_objects())
|
||||
@settings(max_examples=500)
|
||||
def test_is_copyable(self, geospatial: Any) -> None:
|
||||
"""Tests that copies can be made and are equal to the original."""
|
||||
|
||||
# Check copy
|
||||
copied = copy(geospatial)
|
||||
self.assertEqual(geospatial, copied)
|
||||
|
||||
# Check deepcopy
|
||||
deep_copied = deepcopy(geospatial)
|
||||
self.assertEqual(geospatial, deep_copied)
|
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)
|
266
pyrate/tests/plan/geometry/primitives/test_polygons.py
Normal file
266
pyrate/tests/plan/geometry/primitives/test_polygons.py
Normal file
@ -0,0 +1,266 @@
|
||||
"""Tests that the polygon classes in :mod:`pyrate.plan.geometry.polygon` work correctly."""
|
||||
|
||||
# Python standard math
|
||||
from math import sqrt
|
||||
|
||||
# Typing
|
||||
from typing import cast
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Geometry
|
||||
from shapely.geometry import Polygon
|
||||
|
||||
# Scientific
|
||||
from numpy import array
|
||||
|
||||
# Scientific testing
|
||||
from numpy.testing import assert_array_less
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import CartesianPolygon
|
||||
from pyrate.plan.geometry import LocationType
|
||||
from pyrate.plan.geometry import PolarLocation
|
||||
from pyrate.plan.geometry import PolarPolygon
|
||||
|
||||
# Test helpers
|
||||
from pyrate.common.testing.strategies.geometry import cartesian_polygons
|
||||
from pyrate.common.testing.strategies.geometry import geo_bearings
|
||||
from pyrate.common.testing.strategies.geometry import polar_locations
|
||||
from pyrate.common.testing.strategies.geometry import polar_polygons
|
||||
|
||||
# Local test helpers
|
||||
from . import is_any_near_special_point
|
||||
from . import simple_property_only_few_examples
|
||||
from . import slow_route_max_examples
|
||||
|
||||
|
||||
class TestPolarPolygons(TestCase):
|
||||
"""Asserts general properties of the polar polygons."""
|
||||
|
||||
@given(polar_polygons())
|
||||
@slow_route_max_examples
|
||||
def test_area_is_non_negative(self, polar_polygon: PolarPolygon) -> None:
|
||||
"""Tests that all areas are non-negative."""
|
||||
self.assertGreaterEqual(polar_polygon.area, 0, "areas must be non-negative")
|
||||
|
||||
@given(polar_polygons())
|
||||
@slow_route_max_examples
|
||||
def test_is_valid(self, polygon: PolarPolygon) -> None:
|
||||
"""Test that the generated polygons are valid."""
|
||||
|
||||
self.assertTrue(polygon.is_valid)
|
||||
|
||||
def test_is_not_valid(self) -> None:
|
||||
"""Test that a known invalid polygon is detected as such."""
|
||||
|
||||
location = PolarLocation(12, 23.999)
|
||||
polygon = PolarPolygon([location, location, location])
|
||||
self.assertFalse(polygon.is_valid)
|
||||
|
||||
@given(polar_polygons(), polar_locations(), st.booleans())
|
||||
@slow_route_max_examples
|
||||
def test_distance_to_vertices_is_non_negative(
|
||||
self, polar_polygon: PolarPolygon, polar_location: PolarLocation, approximate: bool
|
||||
) -> None:
|
||||
"""Tests that all distances to vertices are non-negative."""
|
||||
distance = polar_polygon.distance_to_vertices(polar_location, approximate)
|
||||
self.assertGreaterEqual(distance, 0, "distances must be non-negative")
|
||||
|
||||
@given(polar_polygons(max_vertices=50))
|
||||
@slow_route_max_examples
|
||||
def test_simplification(self, original: PolarPolygon) -> None:
|
||||
"""Checks the the area change is valid and the rough position is preserved."""
|
||||
|
||||
simplified = original.simplify(tolerance=sqrt(original.area) / 10)
|
||||
|
||||
self.assertLessEqual(len(simplified.locations), len(original.locations))
|
||||
self.assertTrue(original.almost_congruent(simplified, rel_tolerance=0.3))
|
||||
|
||||
@given(polar_polygons())
|
||||
@slow_route_max_examples
|
||||
def test_simplification_artificial(self, original: PolarPolygon) -> None:
|
||||
"""This duplicates the first point and looks whether it is removed."""
|
||||
|
||||
locations = original.locations
|
||||
original.locations = [locations[0]] + locations
|
||||
|
||||
simplified = original.simplify(tolerance=sqrt(original.area) / 1000)
|
||||
|
||||
# strictly less, as opposed to test_simplification()
|
||||
self.assertLess(len(simplified.locations), len(original.locations))
|
||||
self.assertTrue(original.almost_congruent(simplified, rel_tolerance=0.05))
|
||||
|
||||
@given(polar_polygons())
|
||||
@simple_property_only_few_examples # this only checks the call signatures so no need for many examples
|
||||
def test_numpy_conversion_invertible(self, polar_polygon: PolarPolygon) -> None:
|
||||
"""Tests that the polygon conversion can be inverted."""
|
||||
recreated = PolarPolygon.from_numpy(
|
||||
polar_polygon.to_numpy(),
|
||||
name=polar_polygon.name,
|
||||
location_type=polar_polygon.location_type,
|
||||
identifier=polar_polygon.identifier,
|
||||
)
|
||||
self.assertEqual(polar_polygon, recreated)
|
||||
|
||||
@given(
|
||||
st.sampled_from(
|
||||
[
|
||||
PolarPolygon(
|
||||
locations=[
|
||||
PolarLocation(latitude=-76.40057132099628, longitude=-171.92454675519284),
|
||||
PolarLocation(latitude=-76.40057132099628, longitude=-171.92454675519284),
|
||||
PolarLocation(latitude=-76.40057132099628, longitude=-171.92454675519284),
|
||||
],
|
||||
name="K",
|
||||
),
|
||||
PolarPolygon(
|
||||
locations=[
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
],
|
||||
location_type=LocationType.TESTING,
|
||||
name="_1",
|
||||
),
|
||||
PolarPolygon(
|
||||
locations=[
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
],
|
||||
name="",
|
||||
),
|
||||
]
|
||||
),
|
||||
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: PolarPolygon, 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.
|
||||
Since the tests are quite flaky due to the underling library, we only test specific polygons.
|
||||
"""
|
||||
# translate
|
||||
translated, back_direction = original.translate(direction, distance)
|
||||
assert_array_less(0.0 - 1e-12, back_direction)
|
||||
assert_array_less(back_direction, 360.0 + 1e-12)
|
||||
|
||||
# translate back
|
||||
translated_translated, back_back_direction = translated.translate(back_direction[0], distance)
|
||||
assert_array_less(0.0 - 1e-12, back_back_direction)
|
||||
assert_array_less(back_back_direction, 360.0 + 1e-12)
|
||||
|
||||
# the method seems to have problems at poles
|
||||
if not is_any_near_special_point(original) and not is_any_near_special_point(translated):
|
||||
# the method is rather rough, so we want to add larger tolerances than usual while checking
|
||||
self.assertAlmostEqual(direction, back_back_direction[0], delta=0.1)
|
||||
self.assertTrue(original.equals_exact(translated_translated, tolerance=0.1))
|
||||
|
||||
def test_non_finite_from_numpy_raises(self) -> None:
|
||||
"""Tests that invalid parameter to :meth:`~PolarPolygon.from_numpy` warn about it."""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarPolygon.from_numpy(array([(1, 2), (2, 4), (4, float("NaN"))]))
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarPolygon.from_numpy(array([(float("Inf"), 2), (2, 4), (4, 1)]))
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarPolygon.from_numpy(array([(1, 2), (2, float("-Inf")), (4, 4)]))
|
||||
|
||||
|
||||
class TestCartesianPolygons(TestCase):
|
||||
"""Asserts general properties of the cartesian polygons."""
|
||||
|
||||
@given(cartesian_polygons())
|
||||
@simple_property_only_few_examples # this only checks the call signatures so no need for many examples
|
||||
def test_numpy_conversion_invertible(self, cartesian_polygon: CartesianPolygon) -> None:
|
||||
"""Tests that the polygon conversion can be inverted."""
|
||||
recreated = CartesianPolygon.from_numpy(
|
||||
cartesian_polygon.to_numpy(),
|
||||
origin=cartesian_polygon.origin,
|
||||
name=cartesian_polygon.name,
|
||||
location_type=cartesian_polygon.location_type,
|
||||
identifier=cartesian_polygon.identifier,
|
||||
)
|
||||
|
||||
self.assertEqual(cartesian_polygon, recreated)
|
||||
|
||||
@given(cartesian_polygons(origin=polar_locations()))
|
||||
@slow_route_max_examples
|
||||
def test_projection_and_back_projection_origin_in_route(
|
||||
self, cartesian_polygon: CartesianPolygon
|
||||
) -> None:
|
||||
"""Test the projection with an origin already being present in the geometry."""
|
||||
recreated = cartesian_polygon.to_polar().to_cartesian(cast(PolarLocation, cartesian_polygon.origin))
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
|
||||
@given(cartesian_polygons(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_polygon: CartesianPolygon, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with an origin being provided."""
|
||||
recreated = cartesian_polygon.to_polar(origin).to_cartesian(origin)
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
|
||||
@given(cartesian_polygons(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_polygon: CartesianPolygon
|
||||
) -> None:
|
||||
"""Test the projection with no origin being given."""
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_polygon.to_polar()
|
||||
|
||||
@given(cartesian_polygons(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_polygon: CartesianPolygon, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with ambiguous origin being provided."""
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_polygon.to_polar(origin)
|
||||
|
||||
@given(cartesian_polygons())
|
||||
@slow_route_max_examples
|
||||
def test_locations_property_attributes(self, cartesian_polygon: CartesianPolygon) -> None:
|
||||
"""Test that all contained locations share the same attributes."""
|
||||
for location in cartesian_polygon.locations:
|
||||
self.assertEqual(location.location_type, cartesian_polygon.location_type)
|
||||
self.assertEqual(location.name, cartesian_polygon.name)
|
||||
self.assertEqual(location.identifier, cartesian_polygon.identifier)
|
||||
self.assertEqual(location.origin, cartesian_polygon.origin)
|
||||
|
||||
@given(cartesian_polygons())
|
||||
@slow_route_max_examples
|
||||
def test_from_shapely_conversion(self, cartesian_polygon: CartesianPolygon) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.polygon.CartesianPolygon.from_shapely` works."""
|
||||
# we only want to compare the coordinates, so create a new instance without the identifier, name, etc.
|
||||
bare = CartesianPolygon.from_numpy(cartesian_polygon.to_numpy())
|
||||
bare_shapely = Polygon(cartesian_polygon.to_numpy())
|
||||
recreated = CartesianPolygon.from_shapely(bare_shapely)
|
||||
self.assertEqual(recreated, bare)
|
||||
|
||||
def test_non_finite_from_numpy_raises(self) -> None:
|
||||
"""Tests that invalid parameter to :meth:`~CartesianPolygon.from_numpy` warn about it."""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianPolygon.from_numpy(array([(1, 2), (2, 4), (4, float("NaN"))]))
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianPolygon.from_numpy(array([(float("Inf"), 2), (2, 4), (4, 1)]))
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianPolygon.from_numpy(array([(1, 2), (2, float("-Inf")), (4, 4)]))
|
266
pyrate/tests/plan/geometry/primitives/test_routes.py
Normal file
266
pyrate/tests/plan/geometry/primitives/test_routes.py
Normal file
@ -0,0 +1,266 @@
|
||||
"""Tests that the route classes in :mod:`pyrate.plan.geometry.route` work correctly."""
|
||||
|
||||
# Typing
|
||||
from typing import cast
|
||||
|
||||
# Generic testing
|
||||
from unittest import TestCase
|
||||
|
||||
# Geometry
|
||||
from shapely.geometry import LineString
|
||||
|
||||
# Scientific
|
||||
from numpy import array
|
||||
|
||||
# Scientific testing
|
||||
from numpy.testing import assert_array_less
|
||||
|
||||
# Hypothesis testing
|
||||
from hypothesis import given
|
||||
import hypothesis.strategies as st
|
||||
|
||||
# Package under test
|
||||
from pyrate.plan.geometry import CartesianLocation
|
||||
from pyrate.plan.geometry import CartesianRoute
|
||||
from pyrate.plan.geometry import LocationType
|
||||
from pyrate.plan.geometry import PolarLocation
|
||||
from pyrate.plan.geometry import PolarRoute
|
||||
|
||||
# Test helpers
|
||||
from pyrate.common.testing.strategies.geometry import cartesian_routes
|
||||
from pyrate.common.testing.strategies.geometry import geo_bearings
|
||||
from pyrate.common.testing.strategies.geometry import polar_locations
|
||||
from pyrate.common.testing.strategies.geometry import polar_routes
|
||||
|
||||
# Local test helpers
|
||||
from . import is_any_near_special_point
|
||||
from . import simple_property_only_few_examples
|
||||
from . import slow_route_max_examples
|
||||
|
||||
|
||||
class TestPolarRoutes(TestCase):
|
||||
"""Asserts general properties of the polar routes."""
|
||||
|
||||
@given(polar_routes())
|
||||
@simple_property_only_few_examples # this only checks the call signatures so no need for many examples
|
||||
def test_numpy_conversion_invertible(self, polar_route: PolarRoute) -> None:
|
||||
"""Tests that the route conversion can be inverted."""
|
||||
recreated = PolarRoute.from_numpy(
|
||||
polar_route.to_numpy(),
|
||||
name=polar_route.name,
|
||||
location_type=polar_route.location_type,
|
||||
identifier=polar_route.identifier,
|
||||
)
|
||||
self.assertEqual(polar_route, recreated)
|
||||
|
||||
@given(polar_routes(), polar_locations(), st.booleans())
|
||||
@slow_route_max_examples
|
||||
def test_distance_to_vertices_is_non_negative(
|
||||
self, polar_route: PolarRoute, polar_location: PolarLocation, approximate: bool
|
||||
) -> None:
|
||||
"""Tests that all distances to vertices are non-negative."""
|
||||
distance = polar_route.distance_to_vertices(polar_location, approximate)
|
||||
self.assertGreaterEqual(distance, 0, "distances must be non-negative")
|
||||
|
||||
@given(polar_routes())
|
||||
@slow_route_max_examples
|
||||
def test_length_is_non_negative(self, polar_route: PolarRoute) -> None:
|
||||
"""Tests that the length of a route is always non-negative."""
|
||||
self.assertGreaterEqual(polar_route.length(), 0, "lengths must be non-negative")
|
||||
|
||||
@given(polar_routes(min_vertices=3, max_vertices=3))
|
||||
@slow_route_max_examples
|
||||
def test_length_values(self, polar_route: PolarRoute) -> None:
|
||||
"""Tests that the length of a route with three locations is plausible."""
|
||||
location_a, location_b, location_c = polar_route.locations
|
||||
distance = location_a.distance(location_b) + location_b.distance(location_c)
|
||||
self.assertAlmostEqual(polar_route.length(), distance, msg="lengths must be non-negative")
|
||||
|
||||
@given(
|
||||
st.sampled_from(
|
||||
[
|
||||
PolarRoute(
|
||||
locations=[
|
||||
PolarLocation(latitude=-76.40057132099628, longitude=-171.92454675519284),
|
||||
PolarLocation(latitude=-76, longitude=-171),
|
||||
],
|
||||
name="K",
|
||||
),
|
||||
PolarRoute(
|
||||
locations=[
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33, longitude=89),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
PolarLocation(latitude=-33.68964326163993, longitude=89.95053943144632),
|
||||
],
|
||||
location_type=LocationType.TESTING,
|
||||
name="_1",
|
||||
),
|
||||
PolarRoute(
|
||||
locations=[
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
PolarLocation(latitude=0.0, longitude=0.0),
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
PolarLocation(latitude=0.0, longitude=0.029771743643124182),
|
||||
],
|
||||
name="",
|
||||
),
|
||||
]
|
||||
),
|
||||
geo_bearings(),
|
||||
st.floats(min_value=1.0, max_value=100_000.0),
|
||||
)
|
||||
def test_translation_is_invertible(self, original: PolarRoute, 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.
|
||||
Since the tests are quite flaky due to the underling library, we only test specific routes.
|
||||
"""
|
||||
|
||||
# translate
|
||||
translated, back_direction = original.translate(direction, distance)
|
||||
assert_array_less(0.0 - 1e-12, back_direction)
|
||||
assert_array_less(back_direction, 360.0 + 1e-12)
|
||||
|
||||
# translate back
|
||||
translated_translated, back_back_direction = translated.translate(back_direction[0], distance)
|
||||
assert_array_less(0.0 - 1e-12, back_back_direction)
|
||||
assert_array_less(back_back_direction, 360.0 + 1e-12)
|
||||
|
||||
# the method seems to have problems at poles
|
||||
if not is_any_near_special_point(original) and not is_any_near_special_point(translated):
|
||||
# the method is rather rough, so we want to add larger tolerances than usual while checking
|
||||
self.assertAlmostEqual(direction, back_back_direction[0], delta=0.1)
|
||||
self.assertTrue(original.equals_exact(translated_translated, tolerance=1e-2 * distance))
|
||||
|
||||
def test_zero_length_route(self) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.route.PolarRoute.__init__` raises an exception."""
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
PolarRoute(locations=[PolarLocation(0.0, 0.0)] * 2)
|
||||
|
||||
def test_non_finite_from_numpy_raises(self) -> None:
|
||||
"""Tests that invalid parameter to :meth:`~PolarRoute.from_numpy` warn about it."""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarRoute.from_numpy(array([(1, 2), (2, 4), (4, float("NaN"))]))
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarRoute.from_numpy(array([(float("Inf"), 2), (2, 4), (4, 1)]))
|
||||
with self.assertRaises(AssertionError):
|
||||
PolarRoute.from_numpy(array([(1, 2), (2, float("-Inf")), (4, 4)]))
|
||||
|
||||
|
||||
class TestCartesianRoutes(TestCase):
|
||||
"""Asserts general properties of the cartesian routes."""
|
||||
|
||||
@given(cartesian_routes())
|
||||
@simple_property_only_few_examples # this only checks the call signatures so no need for many examples
|
||||
def test_numpy_conversion_invertible(self, cartesian_route: CartesianRoute) -> None:
|
||||
"""Tests that the polygon conversion can be inverted."""
|
||||
|
||||
recreated = CartesianRoute.from_numpy(
|
||||
cartesian_route.to_numpy(),
|
||||
origin=cartesian_route.origin,
|
||||
name=cartesian_route.name,
|
||||
location_type=cartesian_route.location_type,
|
||||
identifier=cartesian_route.identifier,
|
||||
)
|
||||
|
||||
self.assertEqual(cartesian_route, recreated)
|
||||
|
||||
@given(cartesian_routes(origin=polar_locations()))
|
||||
@slow_route_max_examples
|
||||
def test_projection_and_back_projection_origin_in_route(self, cartesian_route: CartesianRoute) -> None:
|
||||
"""Test the projection with an origin already being present in the geometry."""
|
||||
|
||||
# Try since generated primitives might cause an exception to be thrown
|
||||
# e.g. if projected routes become length 0
|
||||
try:
|
||||
recreated = cartesian_route.to_polar().to_cartesian(cast(PolarLocation, cartesian_route.origin))
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@given(cartesian_routes(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_route: CartesianRoute, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with an origin being provided."""
|
||||
|
||||
# Try since generated primitives might cause an exception to be thrown
|
||||
# e.g. if projected routes become length 0
|
||||
try:
|
||||
recreated = cartesian_route.to_polar(origin).to_cartesian(origin)
|
||||
self.assertTrue(recreated.equals_exact(recreated, tolerance=1e-6))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
@given(cartesian_routes(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_route: CartesianRoute) -> None:
|
||||
"""Test the projection with no origin being given."""
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_route.to_polar()
|
||||
|
||||
@given(cartesian_routes(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_route: CartesianRoute, origin: PolarLocation
|
||||
) -> None:
|
||||
"""Test the projection with ambiguous origin being provided."""
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
cartesian_route.to_polar(origin)
|
||||
|
||||
@given(cartesian_routes())
|
||||
@slow_route_max_examples
|
||||
def test_locations_property_attributes(self, cartesian_route: CartesianRoute) -> None:
|
||||
"""Test that all contained locations share the same attributes."""
|
||||
|
||||
for location in cartesian_route.locations:
|
||||
self.assertEqual(location.location_type, cartesian_route.location_type)
|
||||
self.assertEqual(location.name, cartesian_route.name)
|
||||
self.assertEqual(location.identifier, cartesian_route.identifier)
|
||||
self.assertEqual(location.origin, cartesian_route.origin)
|
||||
|
||||
@given(cartesian_routes())
|
||||
@slow_route_max_examples
|
||||
def test_from_shapely_conversion(self, cartesian_route: CartesianRoute) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.route.CartesianRoute.from_shapely` works."""
|
||||
|
||||
# we only want to compare the coordinates, so create a new instance without the identifier, name, etc.
|
||||
bare = CartesianRoute.from_numpy(cartesian_route.to_numpy())
|
||||
bare_shapely = LineString(cartesian_route.to_numpy())
|
||||
recreated = CartesianRoute.from_shapely(bare_shapely)
|
||||
self.assertEqual(recreated, bare)
|
||||
|
||||
@given(cartesian_routes())
|
||||
@simple_property_only_few_examples # this only checks very simple additional logic
|
||||
def test_locations_property(self, cartesian_route: CartesianRoute) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.route.CartesianRoute.locations` works."""
|
||||
|
||||
locations = cartesian_route.locations
|
||||
self.assertEqual(len(cartesian_route.coords), len(locations))
|
||||
for i, (x, y) in enumerate(cartesian_route.coords):
|
||||
self.assertEqual(x, locations[i].x)
|
||||
self.assertEqual(y, locations[i].y)
|
||||
|
||||
def test_zero_length_route(self) -> None:
|
||||
"""Test that :meth:`pyrate.plan.geometry.route.CartesianRoute.__init__` raises an exception."""
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
CartesianRoute(locations=[CartesianLocation(0.0, 0.0)] * 2)
|
||||
|
||||
def test_non_finite_from_numpy_raises(self) -> None:
|
||||
"""Tests that invalid parameter to :meth:`~CartesianRoute.from_numpy` warn about it."""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianRoute.from_numpy(array([(1, 2), (2, 4), (4, float("NaN"))]))
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianRoute.from_numpy(array([(float("Inf"), 2), (2, 4), (4, 1)]))
|
||||
with self.assertRaises(AssertionError):
|
||||
CartesianRoute.from_numpy(array([(1, 2), (2, float("-Inf")), (4, 4)]))
|
Reference in New Issue
Block a user