Removed the subdir.
This commit is contained in:
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)]))
|
Reference in New Issue
Block a user