1
0

Added pyrate as a direct dependency.

This commit is contained in:
2022-07-11 23:07:33 +02:00
parent 8c4532dad4
commit c99d517f6f
230 changed files with 21114 additions and 0 deletions

View File

View File

View File

@ -0,0 +1,171 @@
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry.helpers` functions
for calculating differences.
"""
# Python standard
from abc import ABC
from abc import abstractmethod
from math import isfinite
from math import isnan
import warnings
# Typing
from typing import Callable
from typing import Sequence
from typing import Tuple
# Generic testing
from unittest import TestCase
# Numeric testing
from numpy import allclose
from numpy import array
# Hypothesis testing
from hypothesis import given
import hypothesis.strategies as st
# Test helpers
from pyrate.plan.geometry.helpers import difference_direction
from pyrate.plan.geometry.helpers import difference_latitude
from pyrate.plan.geometry.helpers import difference_longitude
from pyrate.plan.geometry.helpers import ScalarOrArray
class TestDifference(TestCase, ABC):
"""Makes sure the distance measure is well-behaved.
Keep in mind that it is formally not a metric since the triangle inequality does not hold.
"""
@abstractmethod
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
"""Get the function to be tested."""
@abstractmethod
def _get_max(self) -> float:
"""Get the desired maximum value (inclusive)."""
@abstractmethod
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
"""Get some concrete values to be tested as a sequence of ``(value a, value b, distance between)``."""
@given(st.floats(), st.floats())
def test_distance_measuring_commutes_and_is_in_bounds(self, first: float, second: float) -> None:
"""Assures flipping the sides when calculating distances does not make a significant difference."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
distance_1 = self._get_difference_function()(first, second)
distance_2 = self._get_difference_function()(second, first)
if isfinite(distance_1) and isfinite(distance_1):
# 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)
# make sure the distance is within bounds
self.assertLessEqual(distance_1, self._get_max())
self.assertLessEqual(distance_2, self._get_max())
else:
self.assertTrue(isnan(distance_1))
self.assertTrue(isnan(distance_2))
@given(st.floats())
def test_distance_measuring_to_itself_is_zero(self, thing: float) -> None:
"""Assures flipping the sides when calculating distances does not make a significant difference."""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
distance = self._get_difference_function()(thing, thing)
# make sure the distance is always positive and very close to zero
if isfinite(distance):
self.assertGreaterEqual(distance, 0.0)
self.assertAlmostEqual(distance, 0.0)
else:
self.assertTrue(isnan(distance))
def test_concrete_examples(self) -> None:
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
function = self._get_difference_function()
for index, (value_a, value_b, expected_result) in enumerate(self._get_concrete_examples()):
with self.subTest(f"example triple #{index}"):
self.assertAlmostEqual(function(value_a, value_b), expected_result, delta=1e-12)
def test_concrete_examples_as_array(self) -> None:
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
function = self._get_difference_function()
data = array(self._get_concrete_examples()).T
self.assertTrue(allclose(function(data[0, :], data[1, :]), data[2, :]))
class TestDifferenceLatitude(TestDifference):
"""Tests :func:`pyrate.plan.geometry.helpers.difference_latitude`."""
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
return difference_latitude
def _get_max(self) -> float:
return 180.0
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
return [
(0, 0, 0),
(-90, 90, 180),
(-89.5, 0, 89.5),
(-89.5, 0.5, 90),
(-89.5, -0.5, 89),
(-45, 45, 90),
]
class TestDifferenceLongitude(TestDifference):
"""Tests :func:`pyrate.plan.geometry.helpers.difference_longitude`."""
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
return difference_longitude
def _get_max(self) -> float:
return 180.0
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
return [
(0, 0, 0),
(-90, 90, 180),
(-89.5, 0, 89.5),
(-89.5, 0.5, 90),
(-89.5, -0.5, 89),
(180, -180, 0),
(100, -100, 160),
(-45, 45, 90),
]
class TestDifferenceDirection(TestDifference):
"""Tests :func:`pyrate.plan.geometry.helpers.difference_direction`."""
def _get_difference_function(self) -> Callable[[ScalarOrArray, ScalarOrArray], ScalarOrArray]:
return difference_direction
def _get_max(self) -> float:
return 180.0
def _get_concrete_examples(self) -> Sequence[Tuple[float, float, float]]:
return [
(0, 0, 0),
(-90, 90, 180),
(0, 360, 0),
(10, -10, 20),
(10, 350, 20),
(370, 20, 10),
]
# Do not execute the base class as a test, see https://stackoverflow.com/a/43353680/3753684
del TestDifference

View File

@ -0,0 +1,62 @@
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry.helpers` functions
for calculating distances.
"""
# Python standard library
from datetime import timedelta
from math import radians
# Testing
from unittest import TestCase
# Hypothesis testing
from hypothesis import given
from hypothesis import settings
import hypothesis.strategies as st
# Scientific (testing)
import numpy.testing
# Module under test
from pyrate.plan.geometry.helpers import fast_distance_geo
from pyrate.plan.geometry.helpers import haversine_numpy
# Own geometry
from pyrate.plan.geometry.geospatial import MEAN_EARTH_CIRCUMFERENCE
from pyrate.plan.geometry import PolarLocation
# Test helpers
from pyrate.common.testing.strategies.geometry import geo_bearings
from pyrate.common.testing.strategies.geometry import polar_locations
class TestDistanceCalculation(TestCase):
"""Tests the geographic helper methods."""
@given(polar_locations(), polar_locations())
def test_haversine_formula(self, location_1: PolarLocation, location_2: PolarLocation) -> None:
"""Test the correctness of the haversine formula."""
dist = haversine_numpy(
radians(location_1.latitude),
radians(location_1.longitude),
radians(location_2.latitude),
radians(location_2.longitude),
)
self.assertLessEqual(dist, MEAN_EARTH_CIRCUMFERENCE / 2)
numpy.testing.assert_allclose(location_1.distance(location_2), dist, atol=5.0, rtol=0.01)
@given(polar_locations(), geo_bearings(), st.floats(min_value=0.0, max_value=250_000.0))
@settings(deadline=timedelta(seconds=1.0))
# pylint: disable=no-self-use
def test_fast_distance_geo(self, center: PolarLocation, direction: float, distance: float) -> None:
"""Test the correctness of the fast great-circle approximation."""
other, _ = center.translate(direction, distance)
distance_calculated = fast_distance_geo(
radians(other.latitude),
radians(other.longitude),
radians(center.latitude),
radians(center.longitude),
)
numpy.testing.assert_allclose(distance, distance_calculated, atol=0.5, rtol=0.05)

View File

@ -0,0 +1,182 @@
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry.helpers` functions
for normalization.
"""
# Python standard
from abc import ABC
from abc import abstractmethod
# Typing
from typing import Callable
from typing import Sequence
from typing import Tuple
# Generic testing
from unittest import TestCase
# Numeric testing
from numpy import allclose
from numpy import array
# Hypothesis testing
from hypothesis import given
import hypothesis.strategies as st
# Test helpers
from pyrate.plan.geometry.helpers import normalize_direction
from pyrate.plan.geometry.helpers import normalize_latitude
from pyrate.plan.geometry.helpers import normalize_longitude
from pyrate.plan.geometry.helpers import ScalarOrArray
class TestNormalize(TestCase, ABC):
"""Makes sure the normalizations are well-behaved."""
@abstractmethod
def _get_normalization_function(self) -> Callable[[ScalarOrArray], ScalarOrArray]:
"""Get the function to be tested."""
@abstractmethod
def _get_min(self) -> float:
"""Get the desired minimum value (inclusive)."""
@abstractmethod
def _get_max(self) -> float:
"""Get the desired maximum value, see :meth:`TestNormalize._max_is_inclusive`."""
def _max_is_inclusive(self) -> bool: # pylint: disable=no-self-use
"""If :meth:`TestNormalize._get_max` is to be seen as inclusive or exclusive"""
return False
@abstractmethod
def _get_concrete_examples(self) -> Sequence[Tuple[float, float]]:
"""Get some concrete values to be tested as a sequence of ``(non-normalized, normalized)``."""
@given(st.floats(allow_infinity=False, allow_nan=False))
def test_bounds(self, value: float) -> None:
"""Assures that the normalized value is within its bounds."""
normalized = self._get_normalization_function()(value)
# make sure the normalized value is within bounds
self.assertGreaterEqual(normalized, self._get_min())
if self._max_is_inclusive():
self.assertLessEqual(normalized, self._get_max())
else:
self.assertLess(normalized, self._get_max())
@given(st.floats(allow_infinity=False, allow_nan=False))
def test_normalizing_twice(self, value: float) -> None:
"""Assures that normalizing twice does not really change the value."""
normalized = self._get_normalization_function()(value)
normalized_twice = self._get_normalization_function()(normalized)
self.assertAlmostEqual(normalized, normalized_twice, places=10)
@given(st.floats(min_value=-400, max_value=+400))
def test_already_normalized_values(self, value: float) -> None:
"""Assures that values stay unchanged if and only if are already normalized (i.e. within bounds)."""
below_max = value < self._get_max() or (self._max_is_inclusive() and value == self._get_max())
if self._get_min() <= value and below_max:
self.assertAlmostEqual(self._get_normalization_function()(value), value, delta=1e-12)
else:
self.assertNotEqual(self._get_normalization_function()(value), value)
def test_concrete_examples(self) -> None:
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
function = self._get_normalization_function()
for index, (non_normalized, normalized) in enumerate(self._get_concrete_examples()):
with self.subTest(f"example triple #{index}"):
self.assertAlmostEqual(function(non_normalized), normalized, delta=1e-12)
def test_concrete_examples_as_array(self) -> None:
"""Checks the result for the concrete examples given in :meth:`~_get_concrete_examples`."""
function = self._get_normalization_function()
data = array(self._get_concrete_examples()).T
self.assertTrue(allclose(function(data[0, :]), data[1, :]))
class TestNormalizeLatitude(TestNormalize):
"""Tests :func:`pyrate.plan.geometry.helpers.normalize_latitude`."""
def _get_normalization_function(self) -> Callable[[ScalarOrArray], ScalarOrArray]:
return normalize_latitude
def _get_min(self) -> float:
return -90.0
def _get_max(self) -> float:
return 90.0
def _max_is_inclusive(self) -> bool:
return True
def _get_concrete_examples(self) -> Sequence[Tuple[float, float]]:
return [
(0, 0),
(90, 90),
(-90, -90),
(100, 80),
(180, 0),
(270, -90),
(-180, 0),
(-270, 90),
(-10, -10),
]
class TestNormalizeLongitude(TestNormalize):
"""Tests :func:`pyrate.plan.geometry.helpers.normalize_longitude`."""
def _get_normalization_function(self) -> Callable[[ScalarOrArray], ScalarOrArray]:
return normalize_longitude
def _get_min(self) -> float:
return -180.0
def _get_max(self) -> float:
return 180.0
def _get_concrete_examples(self) -> Sequence[Tuple[float, float]]:
return [
(0, 0),
(90, 90),
(-90, -90),
(100, 100),
(180, -180),
(-180, -180),
(270, -90),
(-10, -10),
]
class TestNormalizeDirection(TestNormalize):
"""Tests :func:`pyrate.plan.geometry.helpers.normalize_direction`."""
def _get_normalization_function(self) -> Callable[[ScalarOrArray], ScalarOrArray]:
return normalize_direction
def _get_min(self) -> float:
return 0.0
def _get_max(self) -> float:
return 360.0
def _get_concrete_examples(self) -> Sequence[Tuple[float, float]]:
return [
(0, 0),
(90, 90),
(-90, 270),
(100, 100),
(180, 180),
(-180, 180),
(270, 270),
(-10, 350),
]
# Do not execute the base class as a test, see https://stackoverflow.com/a/43353680/3753684
del TestNormalize

View File

@ -0,0 +1,120 @@
"""This module asserts correct runtime behaviour of various additional helpers."""
# Python Standard Library
from math import tau
# Generic testing
from unittest import TestCase
# Hypothesis testing
import hypothesis.extra.numpy as st_numpy
from hypothesis import given
import hypothesis.strategies as st
# Scientific
import numpy as np
from numpy.testing import assert_almost_equal
# Module under test
from pyrate.plan.geometry.helpers import cartesian_to_spherical
from pyrate.plan.geometry.helpers import difference_latitude
from pyrate.plan.geometry.helpers import difference_longitude
from pyrate.plan.geometry.helpers import mean_angle
from pyrate.plan.geometry.helpers import mean_coordinate
from pyrate.plan.geometry.helpers import meters2rad
from pyrate.plan.geometry.helpers import rad2meters
# Own strategies
from pyrate.common.testing.strategies.geometry import geo_bearings
_POSITIVE_FLOATS = st.floats(min_value=0.0, max_value=1e9, allow_infinity=False, allow_nan=False)
class TestRadiansAndMeterConversion(TestCase):
"""Makes sure the conversion between meters and radians works."""
@given(_POSITIVE_FLOATS)
def test_is_reversible_float(self, meters: float) -> None:
"""Tests that the two functions are the reverse of each other."""
self.assertAlmostEqual(meters, rad2meters(meters2rad(meters)), places=5)
@given(st_numpy.arrays(dtype=float, shape=st_numpy.array_shapes(), elements=_POSITIVE_FLOATS))
def test_is_reversible_numpy(self, meters: np.ndarray) -> None: # pylint: disable=no-self-use
"""Tests that the two functions are the reverse of each other."""
assert_almost_equal(meters, rad2meters(meters2rad(meters)), decimal=5)
class TestCartesianToSpherical(TestCase):
"""Makes sure the conversion from cartesian to spherical coordinates works."""
def test_raises_if_not_on_unit_sphere(self) -> None:
"""Asserts that an exception is raised if values are not on the unit sphere."""
with self.assertRaises(AssertionError):
cartesian_to_spherical(np.array([(10, 20, 30)]))
def test_specific_values(self) -> None: # pylint: disable=no-self-use
"""Asserts that an exception is raised if values are not on the unit sphere."""
data_in = np.array([(1, 0, 0), (0, 1, 0), (0, 0, 1), (0.5, 0.5, np.sqrt(1 - 0.5**2 - 0.5**2))])
expected_data_out = np.array([(0, 0), (0, np.pi / 2), (-np.pi / 2, 0), (-np.pi / 4, np.pi / 4)]).T
assert_almost_equal(cartesian_to_spherical(data_in), expected_data_out)
class TestAngleAndCoordinateMean(TestCase):
"""Makes sure the mean computation and angles and coordinates works correctly."""
@given(geo_bearings(), st.floats(min_value=0.0, max_value=1e-9))
def test_raises_if_ambiguous(self, angle: float, noise: float) -> None:
"""Asserts that an exception is raised if no sensible mean can be calculated."""
ambiguous_pair = np.array([angle, (angle + 180 + noise) % 360])
with self.assertRaises(ValueError):
mean_angle(np.radians(ambiguous_pair))
with self.assertRaises(ValueError):
mean_coordinate(np.array([0.0, 67.2]), ambiguous_pair)
# But the methods should recover from an exception on the latitude mean computation
latitude, _ = mean_coordinate(ambiguous_pair, np.array([0.0, 67.2]))
self.assertAlmostEqual(latitude, 0.0)
@given(
st_numpy.arrays(
elements=st.floats(min_value=0.0, max_value=np.pi), dtype=float, shape=st_numpy.array_shapes()
)
)
def test_mean_angle_is_in_valid_range(self, data: np.ndarray) -> None:
"""Asserts that means are never negative and always between ``0°`` and ``360°``."""
try:
mean = mean_angle(data)
self.assertGreaterEqual(mean, 0.0)
self.assertLessEqual(mean, np.pi)
except ValueError:
pass # this might happen with the generated values and is okay
@given(geo_bearings(), st.floats(min_value=0.0, max_value=170))
def test_obvious_values_angle(self, angle: float, difference: float) -> None:
"""Asserts that the result is sensible for known values."""
mean = mean_angle(np.radians(np.array([angle, (angle + difference) % 360])))
self.assertAlmostEqual(mean, np.radians((angle + difference / 2)) % tau, delta=1e-6)
@given(
st.floats(min_value=-80.0, max_value=+80.0),
st.floats(min_value=-170.0, max_value=+170.0),
st.floats(min_value=-9.0, max_value=9.0),
st.floats(min_value=-9.0, max_value=9.0),
)
def test_obvious_values_coordinate(
self, latitude: float, longitude: float, lat_delta: float, lon_delta: float
) -> None:
"""Asserts that the result is sensible for known values."""
lat_mean, lon_mean = mean_coordinate(
latitudes=np.array([latitude, latitude + lat_delta]),
longitudes=np.array([longitude, longitude + lon_delta]),
)
self.assertLessEqual(difference_latitude(lat_mean, (latitude + lat_delta / 2)), 1e-6)
self.assertLessEqual(difference_longitude(lon_mean, (longitude + lon_delta / 2)), 1e-6)

View File

@ -0,0 +1,34 @@
"""This module asserts correct runtime behaviour of the :mod:`pyrate.plan.geometry.helpers` functions
for translation.
Note that most of the correctness is asserted by the use in
:meth:`pyrate.plan.geometry.PolarPolygon.translate` and :meth:`pyrate.plan.geometry.PolarRoute.translate`.
Also, no extensive tests are needed since we trust the underlying library due to its widespread adoption and
maturity.
We only need to check that the conversion of parameters and results works as expcted.
"""
# Testing
from unittest import TestCase
# Scientific (testing)
from numpy import array
# Module under test
from pyrate.plan.geometry.helpers import translate_numpy
class TestTranslate(TestCase):
"""Tests the translation helpers."""
COORDINATES = array([[1.0, 2.0], [3.0, -4.0], [-5.0, 6.0]])
DIRECTIONS = array([0.0, 90.0, -90.0])
DISTANCES = array([1.0, 100.0, 10000.0])
def test_translate_numpy(self) -> None: # pylint: disable=no-self-use
"""Test that any combination of types of input are accepted."""
translate_numpy(TestTranslate.COORDINATES, TestTranslate.DIRECTIONS, TestTranslate.DISTANCES)
translate_numpy(TestTranslate.COORDINATES, 90, TestTranslate.DISTANCES)
translate_numpy(TestTranslate.COORDINATES, TestTranslate.DIRECTIONS, 100)
translate_numpy(TestTranslate.COORDINATES, 90, 100)

View 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)

View 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))

View 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)

View 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)

View 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)]))

View 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)]))

View File

View File

@ -0,0 +1,376 @@
OFF
122 252 0
0 0.5257311121191336 0.85065080835204
0 0.5257311121191336 -0.85065080835204
0 -0.5257311121191336 0.85065080835204
0 -0.5257311121191336 -0.85065080835204
0.5257311121191336 0.85065080835204 0
0.5257311121191336 -0.85065080835204 0
-0.5257311121191336 0.85065080835204 0
-0.5257311121191336 -0.85065080835204 0
0.85065080835204 0 0.5257311121191336
0.85065080835204 0 -0.5257311121191336
-0.85065080835204 0 0.5257311121191336
-0.85065080835204 0 -0.5257311121191336
2.175242402100701e-16 -1.643460219210441e-32 1
0.3090169943749475 0.8090169943749472 0.5000000000000002
-0.3090169943749475 0.8090169943749472 0.5000000000000002
0.4999999999999999 0.3090169943749474 0.8090169943749472
-0.5000000000000001 0.3090169943749475 0.8090169943749472
2.175242402100701e-16 1.643460219210441e-32 -1
0.3090169943749475 0.8090169943749472 -0.5000000000000002
-0.3090169943749475 0.8090169943749472 -0.5000000000000002
0.5 0.3090169943749473 -0.8090169943749475
-0.4999999999999999 0.3090169943749474 -0.8090169943749472
0.3090169943749473 -0.8090169943749475 0.5
-0.3090169943749475 -0.8090169943749472 0.5000000000000002
0.5 -0.3090169943749473 0.8090169943749475
-0.4999999999999999 -0.3090169943749474 0.8090169943749472
0.3090169943749473 -0.8090169943749475 -0.5
-0.3090169943749473 -0.8090169943749475 -0.5
0.5 -0.3090169943749472 -0.8090169943749475
-0.5000000000000001 -0.3090169943749475 -0.8090169943749472
0 1 4.350484804201401e-17
0.8090169943749475 0.5 0.3090169943749472
0.8090169943749472 0.4999999999999999 -0.3090169943749473
0 -1 -4.350484804201401e-17
0.8090169943749472 -0.4999999999999999 0.3090169943749473
0.8090169943749475 -0.5 -0.3090169943749472
-0.8090169943749472 0.4999999999999999 0.3090169943749473
-0.8090169943749472 0.4999999999999999 -0.3090169943749475
-0.8090169943749475 -0.5 0.3090169943749472
-0.8090169943749472 -0.4999999999999999 -0.3090169943749473
1 2.175242402100701e-16 -1.643460219210441e-32
-1 -2.175242402100701e-16 -1.643460219210441e-32
-0.1803319730021167 0.289241011911498 -0.9401170227910867
-0.35682208977309 -3.124513936890529e-17 -0.9341723589627157
-0.1803319730021166 -0.2892410119114981 -0.9401170227910867
-0.6483337612153338 -5.436311068297173e-17 -0.7613562464893677
-0.1803319730021166 0.2892410119114981 0.9401170227910867
-0.35682208977309 3.09531117213564e-17 0.9341723589627158
-0.6483337612153338 5.402340711901317e-17 0.7613562464893677
-0.1803319730021167 -0.289241011911498 0.9401170227910867
0.291783261575753 -0.5810242734872509 0.7597850497889703
0.5773502691896258 -0.5773502691896256 0.5773502691896258
0.5810242734872511 -0.7597850497889701 0.291783261575753
0.7597850497889702 -0.291783261575753 0.5810242734872511
-0.291783261575753 -0.5810242734872509 -0.7597850497889703
-0.5773502691896258 -0.5773502691896256 -0.5773502691896258
-0.5810242734872511 -0.7597850497889701 -0.291783261575753
-0.7597850497889702 -0.291783261575753 -0.5810242734872511
-2.313323858849861e-18 0.7613562464893674 -0.6483337612153339
3.124513936890529e-17 0.9341723589627158 -0.3568220897730901
-0.2892410119114981 0.9401170227910867 -0.1803319730021165
0.2892410119114981 0.9401170227910867 -0.1803319730021165
-2.313323858849861e-18 -0.7613562464893674 0.6483337612153339
3.124513936890529e-17 -0.9341723589627158 0.3568220897730901
-0.2892410119114981 -0.9401170227910867 0.1803319730021165
0.2892410119114981 -0.9401170227910867 0.1803319730021165
0.2917832615757529 -0.5810242734872509 -0.7597850497889704
0.5773502691896258 -0.5773502691896257 -0.5773502691896258
0.7597850497889701 -0.2917832615757531 -0.5810242734872512
0.5810242734872511 -0.7597850497889701 -0.291783261575753
2.313323858849861e-18 0.7613562464893674 0.6483337612153339
-3.124513936890529e-17 0.9341723589627158 0.3568220897730901
0.2892410119114981 0.9401170227910867 0.1803319730021165
-0.2892410119114981 0.9401170227910867 0.1803319730021165
-0.2917832615757529 -0.5810242734872509 0.7597850497889704
-0.5773502691896258 -0.5773502691896257 0.5773502691896258
-0.7597850497889701 -0.2917832615757531 0.5810242734872512
-0.5810242734872511 -0.7597850497889701 0.291783261575753
2.313323858849861e-18 -0.7613562464893674 -0.6483337612153339
-3.124513936890529e-17 -0.9341723589627158 -0.3568220897730901
0.2892410119114981 -0.9401170227910867 -0.1803319730021165
-0.2892410119114981 -0.9401170227910867 -0.1803319730021165
0.1803319730021167 0.289241011911498 0.9401170227910867
0.35682208977309 -3.124513936890529e-17 0.9341723589627157
0.1803319730021166 -0.2892410119114981 0.9401170227910867
0.6483337612153338 -5.436311068297173e-17 0.7613562464893677
0.2917832615757529 0.5810242734872509 0.7597850497889704
0.5773502691896258 0.5773502691896257 0.5773502691896258
0.7597850497889701 0.2917832615757531 0.5810242734872512
0.5810242734872511 0.7597850497889701 0.291783261575753
0.7613562464893677 -0.6483337612153338 5.436311068297173e-17
0.9341723589627157 -0.35682208977309 3.124513936890529e-17
0.9401170227910867 -0.1803319730021167 -0.289241011911498
0.9401170227910867 -0.1803319730021166 0.2892410119114981
0.291783261575753 0.5810242734872509 -0.7597850497889703
0.5773502691896258 0.5773502691896256 -0.5773502691896258
0.5810242734872511 0.7597850497889701 -0.291783261575753
0.7597850497889702 0.291783261575753 -0.5810242734872511
0.1803319730021166 0.2892410119114981 -0.9401170227910867
0.35682208977309 3.09531117213564e-17 -0.9341723589627158
0.6483337612153338 5.402340711901317e-17 -0.7613562464893677
0.1803319730021167 -0.289241011911498 -0.9401170227910867
0.7613562464893677 0.6483337612153338 -5.436311068297173e-17
0.9341723589627157 0.35682208977309 -3.124513936890529e-17
0.9401170227910867 0.1803319730021167 0.289241011911498
0.9401170227910867 0.1803319730021166 -0.2892410119114981
-0.291783261575753 0.5810242734872509 0.7597850497889703
-0.5773502691896258 0.5773502691896256 0.5773502691896258
-0.5810242734872511 0.7597850497889701 0.291783261575753
-0.7597850497889702 0.291783261575753 0.5810242734872511
-0.7613562464893677 0.6483337612153338 5.436311068297173e-17
-0.9341723589627157 0.35682208977309 3.124513936890529e-17
-0.9401170227910867 0.1803319730021167 -0.289241011911498
-0.9401170227910867 0.1803319730021166 0.2892410119114981
-0.2917832615757529 0.5810242734872509 -0.7597850497889704
-0.5773502691896258 0.5773502691896257 -0.5773502691896258
-0.7597850497889701 0.2917832615757531 -0.5810242734872512
-0.5810242734872511 0.7597850497889701 -0.291783261575753
-0.7613562464893677 -0.6483337612153338 -5.436311068297173e-17
-0.9341723589627157 -0.35682208977309 -3.124513936890529e-17
-0.9401170227910867 -0.1803319730021167 0.289241011911498
-0.9401170227910867 -0.1803319730021166 -0.2892410119114981
3 42 1 98 0.90196 0.45098 0.00000
3 21 114 42 0.90196 0.45098 0.00000
3 42 43 21 0.90196 0.45098 0.00000
3 43 42 17 0.90196 0.45098 0.00000
3 17 44 43 0.90196 0.45098 0.00000
3 44 17 101 0.90196 0.45098 0.00000
3 45 21 43 0.90196 0.45098 0.00000
3 43 29 45 0.90196 0.45098 0.00000
3 29 43 44 0.90196 0.45098 0.00000
3 54 44 3 0.90196 0.45098 0.00000
3 11 116 45 0.90196 0.45098 0.00000
3 57 45 29 0.90196 0.45098 0.00000
3 46 0 106 0.90196 0.45098 0.00000
3 12 82 46 0.90196 0.45098 0.00000
3 46 47 12 0.90196 0.45098 0.00000
3 47 46 16 0.90196 0.45098 0.00000
3 16 48 47 0.90196 0.45098 0.00000
3 48 16 109 0.90196 0.45098 0.00000
3 49 12 47 0.90196 0.45098 0.00000
3 47 25 49 0.90196 0.45098 0.00000
3 25 47 48 0.90196 0.45098 0.00000
3 76 48 10 0.90196 0.45098 0.00000
3 2 84 49 0.90196 0.45098 0.00000
3 74 49 25 0.90196 0.45098 0.00000
3 50 2 62 0.90196 0.45098 0.00000
3 24 84 50 0.90196 0.45098 0.00000
3 50 51 24 0.90196 0.45098 0.00000
3 51 50 22 0.90196 0.45098 0.00000
3 22 52 51 0.90196 0.45098 0.00000
3 52 22 65 0.90196 0.45098 0.00000
3 53 24 51 0.90196 0.45098 0.00000
3 51 34 53 0.90196 0.45098 0.00000
3 34 51 52 0.90196 0.45098 0.00000
3 90 52 5 0.90196 0.45098 0.00000
3 8 85 53 0.90196 0.45098 0.00000
3 93 53 34 0.90196 0.45098 0.00000
3 54 3 78 0.90196 0.45098 0.00000
3 29 44 54 0.90196 0.45098 0.00000
3 54 55 29 0.90196 0.45098 0.00000
3 55 54 27 0.90196 0.45098 0.00000
3 27 56 55 0.90196 0.45098 0.00000
3 56 27 81 0.90196 0.45098 0.00000
3 57 29 55 0.90196 0.45098 0.00000
3 55 39 57 0.90196 0.45098 0.00000
3 39 55 56 0.90196 0.45098 0.00000
3 118 56 7 0.90196 0.45098 0.00000
3 11 45 57 0.90196 0.45098 0.00000
3 121 57 39 0.90196 0.45098 0.00000
3 58 1 114 0.90196 0.45098 0.00000
3 18 94 58 0.90196 0.45098 0.00000
3 58 59 18 0.90196 0.45098 0.00000
3 59 58 19 0.90196 0.45098 0.00000
3 19 60 59 0.90196 0.45098 0.00000
3 60 19 117 0.90196 0.45098 0.00000
3 61 18 59 0.90196 0.45098 0.00000
3 59 30 61 0.90196 0.45098 0.00000
3 30 59 60 0.90196 0.45098 0.00000
3 73 60 6 0.90196 0.45098 0.00000
3 4 96 61 0.90196 0.45098 0.00000
3 72 61 30 0.90196 0.45098 0.00000
3 62 2 74 0.90196 0.45098 0.00000
3 22 50 62 0.90196 0.45098 0.00000
3 62 63 22 0.90196 0.45098 0.00000
3 63 62 23 0.90196 0.45098 0.00000
3 23 64 63 0.90196 0.45098 0.00000
3 64 23 77 0.90196 0.45098 0.00000
3 65 22 63 0.90196 0.45098 0.00000
3 63 33 65 0.90196 0.45098 0.00000
3 33 63 64 0.90196 0.45098 0.00000
3 81 64 7 0.90196 0.45098 0.00000
3 5 52 65 0.90196 0.45098 0.00000
3 80 65 33 0.90196 0.45098 0.00000
3 66 3 101 0.90196 0.45098 0.00000
3 26 78 66 0.90196 0.45098 0.00000
3 66 67 26 0.90196 0.45098 0.00000
3 67 66 28 0.90196 0.45098 0.00000
3 28 68 67 0.90196 0.45098 0.00000
3 68 28 100 0.90196 0.45098 0.00000
3 69 26 67 0.90196 0.45098 0.00000
3 67 35 69 0.90196 0.45098 0.00000
3 35 67 68 0.90196 0.45098 0.00000
3 92 68 9 0.90196 0.45098 0.00000
3 5 80 69 0.90196 0.45098 0.00000
3 90 69 35 0.90196 0.45098 0.00000
3 70 0 86 0.90196 0.45098 0.00000
3 14 106 70 0.90196 0.45098 0.00000
3 70 71 14 0.90196 0.45098 0.00000
3 71 70 13 0.90196 0.45098 0.00000
3 13 72 71 0.90196 0.45098 0.00000
3 72 13 89 0.90196 0.45098 0.00000
3 73 14 71 0.90196 0.45098 0.00000
3 71 30 73 0.90196 0.45098 0.00000
3 30 71 72 0.90196 0.45098 0.00000
3 61 72 4 0.90196 0.45098 0.00000
3 6 108 73 0.90196 0.45098 0.00000
3 60 73 30 0.90196 0.45098 0.00000
3 74 2 49 0.90196 0.45098 0.00000
3 23 62 74 0.90196 0.45098 0.00000
3 74 75 23 0.90196 0.45098 0.00000
3 75 74 25 0.90196 0.45098 0.00000
3 25 76 75 0.90196 0.45098 0.00000
3 76 25 48 0.90196 0.45098 0.00000
3 77 23 75 0.90196 0.45098 0.00000
3 75 38 77 0.90196 0.45098 0.00000
3 38 75 76 0.90196 0.45098 0.00000
3 120 76 10 0.90196 0.45098 0.00000
3 7 64 77 0.90196 0.45098 0.00000
3 118 77 38 0.90196 0.45098 0.00000
3 78 3 66 0.90196 0.45098 0.00000
3 27 54 78 0.90196 0.45098 0.00000
3 78 79 27 0.90196 0.45098 0.00000
3 79 78 26 0.90196 0.45098 0.00000
3 26 80 79 0.90196 0.45098 0.00000
3 80 26 69 0.90196 0.45098 0.00000
3 81 27 79 0.90196 0.45098 0.00000
3 79 33 81 0.90196 0.45098 0.00000
3 33 79 80 0.90196 0.45098 0.00000
3 65 80 5 0.90196 0.45098 0.00000
3 7 56 81 0.90196 0.45098 0.00000
3 64 81 33 0.90196 0.45098 0.00000
3 82 0 46 0.90196 0.45098 0.00000
3 15 86 82 0.90196 0.45098 0.00000
3 82 83 15 0.90196 0.45098 0.00000
3 83 82 12 0.90196 0.45098 0.00000
3 12 84 83 0.90196 0.45098 0.00000
3 84 12 49 0.90196 0.45098 0.00000
3 85 15 83 0.90196 0.45098 0.00000
3 83 24 85 0.90196 0.45098 0.00000
3 24 83 84 0.90196 0.45098 0.00000
3 50 84 2 0.90196 0.45098 0.00000
3 8 88 85 0.90196 0.45098 0.00000
3 53 85 24 0.90196 0.45098 0.00000
3 86 0 82 0.90196 0.45098 0.00000
3 13 70 86 0.90196 0.45098 0.00000
3 86 87 13 0.90196 0.45098 0.00000
3 87 86 15 0.90196 0.45098 0.00000
3 15 88 87 0.90196 0.45098 0.00000
3 88 15 85 0.90196 0.45098 0.00000
3 89 13 87 0.90196 0.45098 0.00000
3 87 31 89 0.90196 0.45098 0.00000
3 31 87 88 0.90196 0.45098 0.00000
3 104 88 8 0.90196 0.45098 0.00000
3 4 72 89 0.90196 0.45098 0.00000
3 102 89 31 0.90196 0.45098 0.00000
3 90 5 69 0.90196 0.45098 0.00000
3 34 52 90 0.90196 0.45098 0.00000
3 90 91 34 0.90196 0.45098 0.00000
3 91 90 35 0.90196 0.45098 0.00000
3 35 92 91 0.90196 0.45098 0.00000
3 92 35 68 0.90196 0.45098 0.00000
3 93 34 91 0.90196 0.45098 0.00000
3 91 40 93 0.90196 0.45098 0.00000
3 40 91 92 0.90196 0.45098 0.00000
3 105 92 9 0.90196 0.45098 0.00000
3 8 53 93 0.90196 0.45098 0.00000
3 104 93 40 0.90196 0.45098 0.00000
3 94 1 58 0.90196 0.45098 0.00000
3 20 98 94 0.90196 0.45098 0.00000
3 94 95 20 0.90196 0.45098 0.00000
3 95 94 18 0.90196 0.45098 0.00000
3 18 96 95 0.90196 0.45098 0.00000
3 96 18 61 0.90196 0.45098 0.00000
3 97 20 95 0.90196 0.45098 0.00000
3 95 32 97 0.90196 0.45098 0.00000
3 32 95 96 0.90196 0.45098 0.00000
3 102 96 4 0.90196 0.45098 0.00000
3 9 100 97 0.90196 0.45098 0.00000
3 105 97 32 0.90196 0.45098 0.00000
3 98 1 94 0.90196 0.45098 0.00000
3 17 42 98 0.90196 0.45098 0.00000
3 98 99 17 0.90196 0.45098 0.00000
3 99 98 20 0.90196 0.45098 0.00000
3 20 100 99 0.90196 0.45098 0.00000
3 100 20 97 0.90196 0.45098 0.00000
3 101 17 99 0.90196 0.45098 0.00000
3 99 28 101 0.90196 0.45098 0.00000
3 28 99 100 0.90196 0.45098 0.00000
3 68 100 9 0.90196 0.45098 0.00000
3 3 44 101 0.90196 0.45098 0.00000
3 66 101 28 0.90196 0.45098 0.00000
3 102 4 89 0.90196 0.45098 0.00000
3 32 96 102 0.90196 0.45098 0.00000
3 102 103 32 0.90196 0.45098 0.00000
3 103 102 31 0.90196 0.45098 0.00000
3 31 104 103 0.90196 0.45098 0.00000
3 104 31 88 0.90196 0.45098 0.00000
3 105 32 103 0.90196 0.45098 0.00000
3 103 40 105 0.90196 0.45098 0.00000
3 40 103 104 0.90196 0.45098 0.00000
3 93 104 8 0.90196 0.45098 0.00000
3 9 97 105 0.90196 0.45098 0.00000
3 92 105 40 0.90196 0.45098 0.00000
3 106 0 70 0.90196 0.45098 0.00000
3 16 46 106 0.90196 0.45098 0.00000
3 106 107 16 0.90196 0.45098 0.00000
3 107 106 14 0.90196 0.45098 0.00000
3 14 108 107 0.90196 0.45098 0.00000
3 108 14 73 0.90196 0.45098 0.00000
3 109 16 107 0.90196 0.45098 0.00000
3 107 36 109 0.90196 0.45098 0.00000
3 36 107 108 0.90196 0.45098 0.00000
3 110 108 6 0.90196 0.45098 0.00000
3 10 48 109 0.90196 0.45098 0.00000
3 113 109 36 0.90196 0.45098 0.00000
3 110 6 117 0.90196 0.45098 0.00000
3 36 108 110 0.90196 0.45098 0.00000
3 110 111 36 0.90196 0.45098 0.00000
3 111 110 37 0.90196 0.45098 0.00000
3 37 112 111 0.90196 0.45098 0.00000
3 112 37 116 0.90196 0.45098 0.00000
3 113 36 111 0.90196 0.45098 0.00000
3 111 41 113 0.90196 0.45098 0.00000
3 41 111 112 0.90196 0.45098 0.00000
3 121 112 11 0.90196 0.45098 0.00000
3 10 109 113 0.90196 0.45098 0.00000
3 120 113 41 0.90196 0.45098 0.00000
3 114 1 42 0.90196 0.45098 0.00000
3 19 58 114 0.90196 0.45098 0.00000
3 114 115 19 0.90196 0.45098 0.00000
3 115 114 21 0.90196 0.45098 0.00000
3 21 116 115 0.90196 0.45098 0.00000
3 116 21 45 0.90196 0.45098 0.00000
3 117 19 115 0.90196 0.45098 0.00000
3 115 37 117 0.90196 0.45098 0.00000
3 37 115 116 0.90196 0.45098 0.00000
3 112 116 11 0.90196 0.45098 0.00000
3 6 60 117 0.90196 0.45098 0.00000
3 110 117 37 0.90196 0.45098 0.00000
3 118 7 77 0.90196 0.45098 0.00000
3 39 56 118 0.90196 0.45098 0.00000
3 118 119 39 0.90196 0.45098 0.00000
3 119 118 38 0.90196 0.45098 0.00000
3 38 120 119 0.90196 0.45098 0.00000
3 120 38 76 0.90196 0.45098 0.00000
3 121 39 119 0.90196 0.45098 0.00000
3 119 41 121 0.90196 0.45098 0.00000
3 41 119 120 0.90196 0.45098 0.00000
3 113 120 10 0.90196 0.45098 0.00000
3 11 57 121 0.90196 0.45098 0.00000
3 112 121 41 0.90196 0.45098 0.00000
1 0 0.38824 0.60000 0.30196
1 1 0.38824 0.60000 0.30196
1 2 0.38824 0.60000 0.30196
1 3 0.38824 0.60000 0.30196
1 4 0.38824 0.60000 0.30196
1 5 0.38824 0.60000 0.30196
1 6 0.38824 0.60000 0.30196
1 7 0.38824 0.60000 0.30196
1 8 0.38824 0.60000 0.30196
1 9 0.38824 0.60000 0.30196
1 10 0.38824 0.60000 0.30196
1 11 0.38824 0.60000 0.30196

View File

@ -0,0 +1,826 @@
OFF
272 552 0
0 0.5257311121191336 0.85065080835204
0 0.5257311121191336 -0.85065080835204
0 -0.5257311121191336 0.85065080835204
0 -0.5257311121191336 -0.85065080835204
0.5257311121191336 0.85065080835204 0
0.5257311121191336 -0.85065080835204 0
-0.5257311121191336 0.85065080835204 0
-0.5257311121191336 -0.85065080835204 0
0.85065080835204 0 0.5257311121191336
0.85065080835204 0 -0.5257311121191336
-0.85065080835204 0 0.5257311121191336
-0.85065080835204 0 -0.5257311121191336
2.267469933117213e-16 0.1834794080019837 0.9830235535526306
1.92245706721902e-16 -0.1834794080019837 0.9830235535526306
0.2120312799176223 0.7385845055044615 0.6399497359677749
0.395510687919606 0.8519810158853969 0.3430738175848559
-0.2120312799176222 0.7385845055044614 0.639949735967775
-0.395510687919606 0.851981015885397 0.3430738175848558
0.3430738175848558 0.395510687919606 0.851981015885397
0.6399497359677748 0.2120312799176223 0.7385845055044618
-0.3430738175848559 0.3955106879196059 0.851981015885397
-0.6399497359677748 0.2120312799176223 0.7385845055044615
1.922457067219021e-16 0.1834794080019837 -0.9830235535526306
2.267469933117213e-16 -0.1834794080019837 -0.9830235535526306
0.2120312799176222 0.7385845055044614 -0.639949735967775
0.395510687919606 0.851981015885397 -0.3430738175848558
-0.2120312799176223 0.7385845055044615 -0.6399497359677749
-0.395510687919606 0.8519810158853969 -0.3430738175848559
0.3430738175848558 0.3955106879196059 -0.8519810158853971
0.6399497359677748 0.2120312799176222 -0.7385845055044618
-0.3430738175848558 0.395510687919606 -0.851981015885397
-0.6399497359677748 0.2120312799176223 -0.7385845055044618
0.2120312799176222 -0.7385845055044615 0.639949735967775
0.3955106879196058 -0.851981015885397 0.3430738175848558
-0.2120312799176223 -0.7385845055044615 0.6399497359677749
-0.395510687919606 -0.8519810158853969 0.3430738175848559
0.3430738175848558 -0.3955106879196059 0.8519810158853971
0.6399497359677748 -0.2120312799176222 0.7385845055044618
-0.3430738175848558 -0.395510687919606 0.851981015885397
-0.6399497359677748 -0.2120312799176223 0.7385845055044618
0.2120312799176222 -0.7385845055044615 -0.639949735967775
0.3955106879196059 -0.851981015885397 -0.3430738175848558
-0.2120312799176222 -0.7385845055044615 -0.639949735967775
-0.3955106879196058 -0.851981015885397 -0.3430738175848558
0.3430738175848558 -0.3955106879196056 -0.851981015885397
0.6399497359677748 -0.2120312799176221 -0.7385845055044618
-0.3430738175848559 -0.3955106879196059 -0.851981015885397
-0.6399497359677748 -0.2120312799176223 -0.7385845055044615
0.1834794080019837 0.9830235535526306 3.4096087162373e-17
-0.1834794080019837 0.9830235535526306 3.4096087162373e-17
0.7385845055044618 0.6399497359677748 0.2120312799176221
0.8519810158853971 0.3430738175848559 0.3955106879196058
0.7385845055044618 0.6399497359677748 -0.2120312799176222
0.851981015885397 0.3430738175848557 -0.3955106879196058
0.1834794080019837 -0.9830235535526306 -3.4096087162373e-17
-0.1834794080019837 -0.9830235535526306 -3.4096087162373e-17
0.7385845055044618 -0.6399497359677748 0.2120312799176222
0.851981015885397 -0.3430738175848557 0.3955106879196058
0.7385845055044618 -0.6399497359677748 -0.2120312799176221
0.8519810158853971 -0.3430738175848559 -0.3955106879196058
-0.7385845055044618 0.6399497359677748 0.2120312799176222
-0.851981015885397 0.3430738175848557 0.3955106879196058
-0.7385845055044615 0.6399497359677748 -0.2120312799176224
-0.8519810158853969 0.3430738175848559 -0.395510687919606
-0.7385845055044618 -0.6399497359677748 0.2120312799176221
-0.8519810158853971 -0.3430738175848559 0.3955106879196058
-0.7385845055044618 -0.6399497359677748 -0.2120312799176222
-0.851981015885397 -0.3430738175848557 -0.3955106879196058
0.9830235535526306 2.267469933117213e-16 0.1834794080019836
0.9830235535526306 1.92245706721902e-16 -0.1834794080019837
-0.9830235535526306 -2.267469933117213e-16 0.1834794080019836
-0.9830235535526306 -1.92245706721902e-16 -0.1834794080019837
-0.1194960329361959 0.374843742971558 -0.919354592345948
-0.2408723836637745 0.1975414971827028 -0.9502409440131212
-0.1235792137159473 -4.626953174201564e-17 -0.9923347106381738
-0.4579792781293658 0.2066706056476467 -0.8646052518724022
-0.35682208977309 -3.124513936890529e-17 -0.9341723589627157
-0.2408723836637744 -0.1975414971827028 -0.9502409440131212
-0.1194960329361959 -0.3748437429715579 -0.919354592345948
-0.5605012402939293 1.966702600323942e-17 -0.8281535845656691
-0.4579792781293658 -0.2066706056476469 -0.8646052518724022
-0.7260059495344062 -6.939177849948745e-18 -0.6876884187192956
-0.1194960329361959 0.3748437429715579 0.919354592345948
-0.2408723836637744 0.1975414971827029 0.9502409440131212
-0.4579792781293657 0.2066706056476468 0.8646052518724022
-0.1235792137159473 4.574573400785218e-17 0.9923347106381738
-0.35682208977309 3.09531117213564e-17 0.9341723589627158
-0.5605012402939293 -1.986949146301587e-17 0.8281535845656691
-0.7260059495344062 7.000242466867724e-18 0.6876884187192956
-0.2408723836637744 -0.1975414971827029 0.9502409440131212
-0.4579792781293658 -0.2066706056476467 0.8646052518724022
-0.1194960329361959 -0.374843742971558 0.919354592345948
0.1933486428115418 -0.5681923857830995 0.7998585594097523
0.3897397037191919 -0.5872812009018947 0.7093685603493469
0.4066259737430365 -0.741026038156455 0.5343554325088083
0.5343554325088083 -0.4066259737430364 0.7410260381564551
0.5773502691896258 -0.5773502691896256 0.5773502691896258
0.5872812009018947 -0.7093685603493469 0.389739703719192
0.5681923857830998 -0.799858559409752 0.1933486428115418
0.7093685603493468 -0.3897397037191919 0.5872812009018948
0.741026038156455 -0.5343554325088083 0.4066259737430364
0.799858559409752 -0.1933486428115418 0.5681923857830997
-0.1933486428115418 -0.5681923857830995 -0.7998585594097523
-0.3897397037191919 -0.5872812009018947 -0.7093685603493469
-0.4066259737430365 -0.741026038156455 -0.5343554325088083
-0.5343554325088083 -0.4066259737430364 -0.7410260381564551
-0.5773502691896258 -0.5773502691896256 -0.5773502691896258
-0.5872812009018947 -0.7093685603493469 -0.389739703719192
-0.5681923857830998 -0.799858559409752 -0.1933486428115418
-0.7093685603493468 -0.3897397037191919 -0.5872812009018948
-0.741026038156455 -0.5343554325088083 -0.4066259737430364
-0.799858559409752 -0.1933486428115418 -0.5681923857830997
2.949150586228217e-17 0.6876884187192956 -0.7260059495344064
-1.966702600323942e-17 0.8281535845656691 -0.5605012402939293
-0.2066706056476468 0.8646052518724022 -0.4579792781293658
0.2066706056476469 0.8646052518724022 -0.4579792781293658
3.124513936890529e-17 0.9341723589627158 -0.3568220897730901
-0.1975414971827029 0.9502409440131212 -0.2408723836637746
-0.374843742971558 0.919354592345948 -0.1194960329361958
0.1975414971827029 0.9502409440131212 -0.2408723836637745
2.197802757745743e-17 0.9923347106381738 -0.1235792137159472
0.374843742971558 0.919354592345948 -0.1194960329361959
2.949150586228217e-17 -0.6876884187192956 0.7260059495344064
-1.966702600323942e-17 -0.8281535845656691 0.5605012402939293
-0.2066706056476468 -0.8646052518724022 0.4579792781293658
0.2066706056476469 -0.8646052518724022 0.4579792781293658
3.124513936890529e-17 -0.9341723589627158 0.3568220897730901
-0.1975414971827029 -0.9502409440131212 0.2408723836637746
-0.374843742971558 -0.919354592345948 0.1194960329361958
0.1975414971827029 -0.9502409440131212 0.2408723836637745
2.197802757745743e-17 -0.9923347106381738 0.1235792137159472
0.374843742971558 -0.919354592345948 0.1194960329361959
0.1933486428115418 -0.5681923857830995 -0.7998585594097523
0.3897397037191918 -0.5872812009018947 -0.7093685603493468
0.5343554325088081 -0.4066259737430364 -0.7410260381564551
0.4066259737430364 -0.741026038156455 -0.5343554325088083
0.5773502691896258 -0.5773502691896257 -0.5773502691896258
0.7093685603493467 -0.389739703719192 -0.5872812009018948
0.799858559409752 -0.1933486428115418 -0.5681923857830997
0.5872812009018947 -0.7093685603493468 -0.3897397037191921
0.7410260381564551 -0.5343554325088083 -0.4066259737430366
0.5681923857830998 -0.799858559409752 -0.1933486428115418
-2.949150586228217e-17 0.6876884187192956 0.7260059495344064
1.966702600323942e-17 0.8281535845656691 0.5605012402939293
0.2066706056476468 0.8646052518724022 0.4579792781293658
-0.2066706056476469 0.8646052518724022 0.4579792781293658
-3.124513936890529e-17 0.9341723589627158 0.3568220897730901
0.1975414971827029 0.9502409440131212 0.2408723836637746
0.374843742971558 0.919354592345948 0.1194960329361958
-0.1975414971827029 0.9502409440131212 0.2408723836637745
-2.197802757745743e-17 0.9923347106381738 0.1235792137159472
-0.374843742971558 0.919354592345948 0.1194960329361959
-0.1933486428115418 -0.5681923857830995 0.7998585594097523
-0.3897397037191918 -0.5872812009018947 0.7093685603493468
-0.5343554325088081 -0.4066259737430364 0.7410260381564551
-0.4066259737430364 -0.741026038156455 0.5343554325088083
-0.5773502691896258 -0.5773502691896257 0.5773502691896258
-0.7093685603493467 -0.389739703719192 0.5872812009018948
-0.799858559409752 -0.1933486428115418 0.5681923857830997
-0.5872812009018947 -0.7093685603493468 0.3897397037191921
-0.7410260381564551 -0.5343554325088083 0.4066259737430366
-0.5681923857830998 -0.799858559409752 0.1933486428115418
-2.949150586228217e-17 -0.6876884187192956 -0.7260059495344064
1.966702600323942e-17 -0.8281535845656691 -0.5605012402939293
0.2066706056476468 -0.8646052518724022 -0.4579792781293658
-0.2066706056476469 -0.8646052518724022 -0.4579792781293658
-3.124513936890529e-17 -0.9341723589627158 -0.3568220897730901
0.1975414971827029 -0.9502409440131212 -0.2408723836637746
0.374843742971558 -0.919354592345948 -0.1194960329361958
-0.1975414971827029 -0.9502409440131212 -0.2408723836637745
-2.197802757745743e-17 -0.9923347106381738 -0.1235792137159472
-0.374843742971558 -0.919354592345948 -0.1194960329361959
0.1194960329361959 0.374843742971558 0.919354592345948
0.2408723836637745 0.1975414971827028 0.9502409440131212
0.1235792137159473 -4.626953174201564e-17 0.9923347106381738
0.4579792781293658 0.2066706056476467 0.8646052518724022
0.35682208977309 -3.124513936890529e-17 0.9341723589627157
0.2408723836637744 -0.1975414971827028 0.9502409440131212
0.1194960329361959 -0.3748437429715579 0.919354592345948
0.5605012402939293 1.966702600323942e-17 0.8281535845656691
0.4579792781293658 -0.2066706056476469 0.8646052518724022
0.7260059495344062 -6.939177849948745e-18 0.6876884187192956
0.1933486428115418 0.5681923857830995 0.7998585594097523
0.3897397037191918 0.5872812009018947 0.7093685603493468
0.5343554325088081 0.4066259737430364 0.7410260381564551
0.4066259737430364 0.741026038156455 0.5343554325088083
0.5773502691896258 0.5773502691896257 0.5773502691896258
0.7093685603493467 0.389739703719192 0.5872812009018948
0.799858559409752 0.1933486428115418 0.5681923857830997
0.5872812009018947 0.7093685603493468 0.3897397037191921
0.7410260381564551 0.5343554325088083 0.4066259737430366
0.5681923857830998 0.799858559409752 0.1933486428115418
0.6876884187192954 -0.7260059495344061 6.939177849948745e-18
0.8281535845656691 -0.5605012402939293 -1.966702600323942e-17
0.8646052518724022 -0.4579792781293658 -0.2066706056476467
0.8646052518724022 -0.4579792781293658 0.2066706056476468
0.9341723589627157 -0.35682208977309 3.124513936890529e-17
0.9502409440131212 -0.2408723836637745 -0.1975414971827029
0.919354592345948 -0.1194960329361959 -0.374843742971558
0.9502409440131212 -0.2408723836637745 0.1975414971827029
0.992334710638174 -0.1235792137159473 4.511279344846526e-17
0.919354592345948 -0.1194960329361959 0.3748437429715579
0.1933486428115418 0.5681923857830995 -0.7998585594097523
0.3897397037191919 0.5872812009018947 -0.7093685603493469
0.4066259737430365 0.741026038156455 -0.5343554325088083
0.5343554325088083 0.4066259737430364 -0.7410260381564551
0.5773502691896258 0.5773502691896256 -0.5773502691896258
0.5872812009018947 0.7093685603493469 -0.389739703719192
0.5681923857830998 0.799858559409752 -0.1933486428115418
0.7093685603493468 0.3897397037191919 -0.5872812009018948
0.741026038156455 0.5343554325088083 -0.4066259737430364
0.799858559409752 0.1933486428115418 -0.5681923857830997
0.1194960329361959 0.3748437429715579 -0.919354592345948
0.2408723836637744 0.1975414971827029 -0.9502409440131212
0.4579792781293657 0.2066706056476468 -0.8646052518724022
0.1235792137159473 4.574573400785218e-17 -0.9923347106381738
0.35682208977309 3.09531117213564e-17 -0.9341723589627158
0.5605012402939293 -1.986949146301587e-17 -0.8281535845656691
0.7260059495344062 7.000242466867724e-18 -0.6876884187192956
0.2408723836637744 -0.1975414971827029 -0.9502409440131212
0.4579792781293658 -0.2066706056476467 -0.8646052518724022
0.1194960329361959 -0.374843742971558 -0.919354592345948
0.6876884187192954 0.7260059495344061 -6.939177849948745e-18
0.8281535845656691 0.5605012402939293 1.966702600323942e-17
0.8646052518724022 0.4579792781293658 0.2066706056476467
0.8646052518724022 0.4579792781293658 -0.2066706056476468
0.9341723589627157 0.35682208977309 -3.124513936890529e-17
0.9502409440131212 0.2408723836637745 0.1975414971827029
0.919354592345948 0.1194960329361959 0.374843742971558
0.9502409440131212 0.2408723836637745 -0.1975414971827029
0.992334710638174 0.1235792137159473 -4.511279344846526e-17
0.919354592345948 0.1194960329361959 -0.3748437429715579
-0.1933486428115418 0.5681923857830995 0.7998585594097523
-0.3897397037191919 0.5872812009018947 0.7093685603493469
-0.4066259737430365 0.741026038156455 0.5343554325088083
-0.5343554325088083 0.4066259737430364 0.7410260381564551
-0.5773502691896258 0.5773502691896256 0.5773502691896258
-0.5872812009018947 0.7093685603493469 0.389739703719192
-0.5681923857830998 0.799858559409752 0.1933486428115418
-0.7093685603493468 0.3897397037191919 0.5872812009018948
-0.741026038156455 0.5343554325088083 0.4066259737430364
-0.799858559409752 0.1933486428115418 0.5681923857830997
-0.6876884187192954 0.7260059495344061 6.939177849948745e-18
-0.8281535845656691 0.5605012402939293 -1.966702600323942e-17
-0.8646052518724022 0.4579792781293658 -0.2066706056476467
-0.8646052518724022 0.4579792781293658 0.2066706056476468
-0.9341723589627157 0.35682208977309 3.124513936890529e-17
-0.9502409440131212 0.2408723836637745 -0.1975414971827029
-0.919354592345948 0.1194960329361959 -0.374843742971558
-0.9502409440131212 0.2408723836637745 0.1975414971827029
-0.992334710638174 0.1235792137159473 4.511279344846526e-17
-0.919354592345948 0.1194960329361959 0.3748437429715579
-0.1933486428115418 0.5681923857830995 -0.7998585594097523
-0.3897397037191918 0.5872812009018947 -0.7093685603493468
-0.5343554325088081 0.4066259737430364 -0.7410260381564551
-0.4066259737430364 0.741026038156455 -0.5343554325088083
-0.5773502691896258 0.5773502691896257 -0.5773502691896258
-0.7093685603493467 0.389739703719192 -0.5872812009018948
-0.799858559409752 0.1933486428115418 -0.5681923857830997
-0.5872812009018947 0.7093685603493468 -0.3897397037191921
-0.7410260381564551 0.5343554325088083 -0.4066259737430366
-0.5681923857830998 0.799858559409752 -0.1933486428115418
-0.6876884187192954 -0.7260059495344061 -6.939177849948745e-18
-0.8281535845656691 -0.5605012402939293 1.966702600323942e-17
-0.8646052518724022 -0.4579792781293658 0.2066706056476467
-0.8646052518724022 -0.4579792781293658 -0.2066706056476468
-0.9341723589627157 -0.35682208977309 -3.124513936890529e-17
-0.9502409440131212 -0.2408723836637745 0.1975414971827029
-0.919354592345948 -0.1194960329361959 0.374843742971558
-0.9502409440131212 -0.2408723836637745 -0.1975414971827029
-0.992334710638174 -0.1235792137159473 -4.511279344846526e-17
-0.919354592345948 -0.1194960329361959 -0.3748437429715579
3 72 1 212 0.90196 0.45098 0.00000
3 30 252 72 0.90196 0.45098 0.00000
3 72 73 30 0.90196 0.45098 0.00000
3 73 72 22 0.90196 0.45098 0.00000
3 22 74 73 0.90196 0.45098 0.00000
3 74 22 215 0.90196 0.45098 0.00000
3 75 30 73 0.90196 0.45098 0.00000
3 73 76 75 0.90196 0.45098 0.00000
3 76 73 74 0.90196 0.45098 0.00000
3 74 77 76 0.90196 0.45098 0.00000
3 77 74 23 0.90196 0.45098 0.00000
3 23 78 77 0.90196 0.45098 0.00000
3 78 23 221 0.90196 0.45098 0.00000
3 31 254 75 0.90196 0.45098 0.00000
3 75 79 31 0.90196 0.45098 0.00000
3 79 75 76 0.90196 0.45098 0.00000
3 76 80 79 0.90196 0.45098 0.00000
3 80 76 77 0.90196 0.45098 0.00000
3 77 46 80 0.90196 0.45098 0.00000
3 46 77 78 0.90196 0.45098 0.00000
3 102 78 3 0.90196 0.45098 0.00000
3 81 31 79 0.90196 0.45098 0.00000
3 79 47 81 0.90196 0.45098 0.00000
3 47 79 80 0.90196 0.45098 0.00000
3 105 80 46 0.90196 0.45098 0.00000
3 11 258 81 0.90196 0.45098 0.00000
3 111 81 47 0.90196 0.45098 0.00000
3 82 0 232 0.90196 0.45098 0.00000
3 12 172 82 0.90196 0.45098 0.00000
3 82 83 12 0.90196 0.45098 0.00000
3 83 82 20 0.90196 0.45098 0.00000
3 20 84 83 0.90196 0.45098 0.00000
3 84 20 235 0.90196 0.45098 0.00000
3 85 12 83 0.90196 0.45098 0.00000
3 83 86 85 0.90196 0.45098 0.00000
3 86 83 84 0.90196 0.45098 0.00000
3 84 87 86 0.90196 0.45098 0.00000
3 87 84 21 0.90196 0.45098 0.00000
3 21 88 87 0.90196 0.45098 0.00000
3 88 21 241 0.90196 0.45098 0.00000
3 13 174 85 0.90196 0.45098 0.00000
3 85 89 13 0.90196 0.45098 0.00000
3 89 85 86 0.90196 0.45098 0.00000
3 86 90 89 0.90196 0.45098 0.00000
3 90 86 87 0.90196 0.45098 0.00000
3 87 39 90 0.90196 0.45098 0.00000
3 39 87 88 0.90196 0.45098 0.00000
3 158 88 10 0.90196 0.45098 0.00000
3 91 13 89 0.90196 0.45098 0.00000
3 89 38 91 0.90196 0.45098 0.00000
3 38 89 90 0.90196 0.45098 0.00000
3 154 90 39 0.90196 0.45098 0.00000
3 2 178 91 0.90196 0.45098 0.00000
3 152 91 38 0.90196 0.45098 0.00000
3 92 2 122 0.90196 0.45098 0.00000
3 36 178 92 0.90196 0.45098 0.00000
3 92 93 36 0.90196 0.45098 0.00000
3 93 92 32 0.90196 0.45098 0.00000
3 32 94 93 0.90196 0.45098 0.00000
3 94 32 125 0.90196 0.45098 0.00000
3 95 36 93 0.90196 0.45098 0.00000
3 93 96 95 0.90196 0.45098 0.00000
3 96 93 94 0.90196 0.45098 0.00000
3 94 97 96 0.90196 0.45098 0.00000
3 97 94 33 0.90196 0.45098 0.00000
3 33 98 97 0.90196 0.45098 0.00000
3 98 33 131 0.90196 0.45098 0.00000
3 37 180 95 0.90196 0.45098 0.00000
3 95 99 37 0.90196 0.45098 0.00000
3 99 95 96 0.90196 0.45098 0.00000
3 96 100 99 0.90196 0.45098 0.00000
3 100 96 97 0.90196 0.45098 0.00000
3 97 56 100 0.90196 0.45098 0.00000
3 56 97 98 0.90196 0.45098 0.00000
3 192 98 5 0.90196 0.45098 0.00000
3 101 37 99 0.90196 0.45098 0.00000
3 99 57 101 0.90196 0.45098 0.00000
3 57 99 100 0.90196 0.45098 0.00000
3 195 100 56 0.90196 0.45098 0.00000
3 8 181 101 0.90196 0.45098 0.00000
3 201 101 57 0.90196 0.45098 0.00000
3 102 3 162 0.90196 0.45098 0.00000
3 46 78 102 0.90196 0.45098 0.00000
3 102 103 46 0.90196 0.45098 0.00000
3 103 102 42 0.90196 0.45098 0.00000
3 42 104 103 0.90196 0.45098 0.00000
3 104 42 165 0.90196 0.45098 0.00000
3 105 46 103 0.90196 0.45098 0.00000
3 103 106 105 0.90196 0.45098 0.00000
3 106 103 104 0.90196 0.45098 0.00000
3 104 107 106 0.90196 0.45098 0.00000
3 107 104 43 0.90196 0.45098 0.00000
3 43 108 107 0.90196 0.45098 0.00000
3 108 43 171 0.90196 0.45098 0.00000
3 47 80 105 0.90196 0.45098 0.00000
3 105 109 47 0.90196 0.45098 0.00000
3 109 105 106 0.90196 0.45098 0.00000
3 106 110 109 0.90196 0.45098 0.00000
3 110 106 107 0.90196 0.45098 0.00000
3 107 66 110 0.90196 0.45098 0.00000
3 66 107 108 0.90196 0.45098 0.00000
3 262 108 7 0.90196 0.45098 0.00000
3 111 47 109 0.90196 0.45098 0.00000
3 109 67 111 0.90196 0.45098 0.00000
3 67 109 110 0.90196 0.45098 0.00000
3 265 110 66 0.90196 0.45098 0.00000
3 11 81 111 0.90196 0.45098 0.00000
3 271 111 67 0.90196 0.45098 0.00000
3 112 1 252 0.90196 0.45098 0.00000
3 24 202 112 0.90196 0.45098 0.00000
3 112 113 24 0.90196 0.45098 0.00000
3 113 112 26 0.90196 0.45098 0.00000
3 26 114 113 0.90196 0.45098 0.00000
3 114 26 255 0.90196 0.45098 0.00000
3 115 24 113 0.90196 0.45098 0.00000
3 113 116 115 0.90196 0.45098 0.00000
3 116 113 114 0.90196 0.45098 0.00000
3 114 117 116 0.90196 0.45098 0.00000
3 117 114 27 0.90196 0.45098 0.00000
3 27 118 117 0.90196 0.45098 0.00000
3 118 27 261 0.90196 0.45098 0.00000
3 25 204 115 0.90196 0.45098 0.00000
3 115 119 25 0.90196 0.45098 0.00000
3 119 115 116 0.90196 0.45098 0.00000
3 116 120 119 0.90196 0.45098 0.00000
3 120 116 117 0.90196 0.45098 0.00000
3 117 49 120 0.90196 0.45098 0.00000
3 49 117 118 0.90196 0.45098 0.00000
3 151 118 6 0.90196 0.45098 0.00000
3 121 25 119 0.90196 0.45098 0.00000
3 119 48 121 0.90196 0.45098 0.00000
3 48 119 120 0.90196 0.45098 0.00000
3 150 120 49 0.90196 0.45098 0.00000
3 4 208 121 0.90196 0.45098 0.00000
3 148 121 48 0.90196 0.45098 0.00000
3 122 2 152 0.90196 0.45098 0.00000
3 32 92 122 0.90196 0.45098 0.00000
3 122 123 32 0.90196 0.45098 0.00000
3 123 122 34 0.90196 0.45098 0.00000
3 34 124 123 0.90196 0.45098 0.00000
3 124 34 155 0.90196 0.45098 0.00000
3 125 32 123 0.90196 0.45098 0.00000
3 123 126 125 0.90196 0.45098 0.00000
3 126 123 124 0.90196 0.45098 0.00000
3 124 127 126 0.90196 0.45098 0.00000
3 127 124 35 0.90196 0.45098 0.00000
3 35 128 127 0.90196 0.45098 0.00000
3 128 35 161 0.90196 0.45098 0.00000
3 33 94 125 0.90196 0.45098 0.00000
3 125 129 33 0.90196 0.45098 0.00000
3 129 125 126 0.90196 0.45098 0.00000
3 126 130 129 0.90196 0.45098 0.00000
3 130 126 127 0.90196 0.45098 0.00000
3 127 55 130 0.90196 0.45098 0.00000
3 55 127 128 0.90196 0.45098 0.00000
3 171 128 7 0.90196 0.45098 0.00000
3 131 33 129 0.90196 0.45098 0.00000
3 129 54 131 0.90196 0.45098 0.00000
3 54 129 130 0.90196 0.45098 0.00000
3 170 130 55 0.90196 0.45098 0.00000
3 5 98 131 0.90196 0.45098 0.00000
3 168 131 54 0.90196 0.45098 0.00000
3 132 3 221 0.90196 0.45098 0.00000
3 40 162 132 0.90196 0.45098 0.00000
3 132 133 40 0.90196 0.45098 0.00000
3 133 132 44 0.90196 0.45098 0.00000
3 44 134 133 0.90196 0.45098 0.00000
3 134 44 220 0.90196 0.45098 0.00000
3 135 40 133 0.90196 0.45098 0.00000
3 133 136 135 0.90196 0.45098 0.00000
3 136 133 134 0.90196 0.45098 0.00000
3 134 137 136 0.90196 0.45098 0.00000
3 137 134 45 0.90196 0.45098 0.00000
3 45 138 137 0.90196 0.45098 0.00000
3 138 45 218 0.90196 0.45098 0.00000
3 41 164 135 0.90196 0.45098 0.00000
3 135 139 41 0.90196 0.45098 0.00000
3 139 135 136 0.90196 0.45098 0.00000
3 136 140 139 0.90196 0.45098 0.00000
3 140 136 137 0.90196 0.45098 0.00000
3 137 59 140 0.90196 0.45098 0.00000
3 59 137 138 0.90196 0.45098 0.00000
3 198 138 9 0.90196 0.45098 0.00000
3 141 41 139 0.90196 0.45098 0.00000
3 139 58 141 0.90196 0.45098 0.00000
3 58 139 140 0.90196 0.45098 0.00000
3 194 140 59 0.90196 0.45098 0.00000
3 5 168 141 0.90196 0.45098 0.00000
3 192 141 58 0.90196 0.45098 0.00000
3 142 0 182 0.90196 0.45098 0.00000
3 16 232 142 0.90196 0.45098 0.00000
3 142 143 16 0.90196 0.45098 0.00000
3 143 142 14 0.90196 0.45098 0.00000
3 14 144 143 0.90196 0.45098 0.00000
3 144 14 185 0.90196 0.45098 0.00000
3 145 16 143 0.90196 0.45098 0.00000
3 143 146 145 0.90196 0.45098 0.00000
3 146 143 144 0.90196 0.45098 0.00000
3 144 147 146 0.90196 0.45098 0.00000
3 147 144 15 0.90196 0.45098 0.00000
3 15 148 147 0.90196 0.45098 0.00000
3 148 15 191 0.90196 0.45098 0.00000
3 17 234 145 0.90196 0.45098 0.00000
3 145 149 17 0.90196 0.45098 0.00000
3 149 145 146 0.90196 0.45098 0.00000
3 146 150 149 0.90196 0.45098 0.00000
3 150 146 147 0.90196 0.45098 0.00000
3 147 48 150 0.90196 0.45098 0.00000
3 48 147 148 0.90196 0.45098 0.00000
3 121 148 4 0.90196 0.45098 0.00000
3 151 17 149 0.90196 0.45098 0.00000
3 149 49 151 0.90196 0.45098 0.00000
3 49 149 150 0.90196 0.45098 0.00000
3 120 150 48 0.90196 0.45098 0.00000
3 6 238 151 0.90196 0.45098 0.00000
3 118 151 49 0.90196 0.45098 0.00000
3 152 2 91 0.90196 0.45098 0.00000
3 34 122 152 0.90196 0.45098 0.00000
3 152 153 34 0.90196 0.45098 0.00000
3 153 152 38 0.90196 0.45098 0.00000
3 38 154 153 0.90196 0.45098 0.00000
3 154 38 90 0.90196 0.45098 0.00000
3 155 34 153 0.90196 0.45098 0.00000
3 153 156 155 0.90196 0.45098 0.00000
3 156 153 154 0.90196 0.45098 0.00000
3 154 157 156 0.90196 0.45098 0.00000
3 157 154 39 0.90196 0.45098 0.00000
3 39 158 157 0.90196 0.45098 0.00000
3 158 39 88 0.90196 0.45098 0.00000
3 35 124 155 0.90196 0.45098 0.00000
3 155 159 35 0.90196 0.45098 0.00000
3 159 155 156 0.90196 0.45098 0.00000
3 156 160 159 0.90196 0.45098 0.00000
3 160 156 157 0.90196 0.45098 0.00000
3 157 65 160 0.90196 0.45098 0.00000
3 65 157 158 0.90196 0.45098 0.00000
3 268 158 10 0.90196 0.45098 0.00000
3 161 35 159 0.90196 0.45098 0.00000
3 159 64 161 0.90196 0.45098 0.00000
3 64 159 160 0.90196 0.45098 0.00000
3 264 160 65 0.90196 0.45098 0.00000
3 7 128 161 0.90196 0.45098 0.00000
3 262 161 64 0.90196 0.45098 0.00000
3 162 3 132 0.90196 0.45098 0.00000
3 42 102 162 0.90196 0.45098 0.00000
3 162 163 42 0.90196 0.45098 0.00000
3 163 162 40 0.90196 0.45098 0.00000
3 40 164 163 0.90196 0.45098 0.00000
3 164 40 135 0.90196 0.45098 0.00000
3 165 42 163 0.90196 0.45098 0.00000
3 163 166 165 0.90196 0.45098 0.00000
3 166 163 164 0.90196 0.45098 0.00000
3 164 167 166 0.90196 0.45098 0.00000
3 167 164 41 0.90196 0.45098 0.00000
3 41 168 167 0.90196 0.45098 0.00000
3 168 41 141 0.90196 0.45098 0.00000
3 43 104 165 0.90196 0.45098 0.00000
3 165 169 43 0.90196 0.45098 0.00000
3 169 165 166 0.90196 0.45098 0.00000
3 166 170 169 0.90196 0.45098 0.00000
3 170 166 167 0.90196 0.45098 0.00000
3 167 54 170 0.90196 0.45098 0.00000
3 54 167 168 0.90196 0.45098 0.00000
3 131 168 5 0.90196 0.45098 0.00000
3 171 43 169 0.90196 0.45098 0.00000
3 169 55 171 0.90196 0.45098 0.00000
3 55 169 170 0.90196 0.45098 0.00000
3 130 170 54 0.90196 0.45098 0.00000
3 7 108 171 0.90196 0.45098 0.00000
3 128 171 55 0.90196 0.45098 0.00000
3 172 0 82 0.90196 0.45098 0.00000
3 18 182 172 0.90196 0.45098 0.00000
3 172 173 18 0.90196 0.45098 0.00000
3 173 172 12 0.90196 0.45098 0.00000
3 12 174 173 0.90196 0.45098 0.00000
3 174 12 85 0.90196 0.45098 0.00000
3 175 18 173 0.90196 0.45098 0.00000
3 173 176 175 0.90196 0.45098 0.00000
3 176 173 174 0.90196 0.45098 0.00000
3 174 177 176 0.90196 0.45098 0.00000
3 177 174 13 0.90196 0.45098 0.00000
3 13 178 177 0.90196 0.45098 0.00000
3 178 13 91 0.90196 0.45098 0.00000
3 19 184 175 0.90196 0.45098 0.00000
3 175 179 19 0.90196 0.45098 0.00000
3 179 175 176 0.90196 0.45098 0.00000
3 176 180 179 0.90196 0.45098 0.00000
3 180 176 177 0.90196 0.45098 0.00000
3 177 36 180 0.90196 0.45098 0.00000
3 36 177 178 0.90196 0.45098 0.00000
3 92 178 2 0.90196 0.45098 0.00000
3 181 19 179 0.90196 0.45098 0.00000
3 179 37 181 0.90196 0.45098 0.00000
3 37 179 180 0.90196 0.45098 0.00000
3 95 180 36 0.90196 0.45098 0.00000
3 8 188 181 0.90196 0.45098 0.00000
3 101 181 37 0.90196 0.45098 0.00000
3 182 0 172 0.90196 0.45098 0.00000
3 14 142 182 0.90196 0.45098 0.00000
3 182 183 14 0.90196 0.45098 0.00000
3 183 182 18 0.90196 0.45098 0.00000
3 18 184 183 0.90196 0.45098 0.00000
3 184 18 175 0.90196 0.45098 0.00000
3 185 14 183 0.90196 0.45098 0.00000
3 183 186 185 0.90196 0.45098 0.00000
3 186 183 184 0.90196 0.45098 0.00000
3 184 187 186 0.90196 0.45098 0.00000
3 187 184 19 0.90196 0.45098 0.00000
3 19 188 187 0.90196 0.45098 0.00000
3 188 19 181 0.90196 0.45098 0.00000
3 15 144 185 0.90196 0.45098 0.00000
3 185 189 15 0.90196 0.45098 0.00000
3 189 185 186 0.90196 0.45098 0.00000
3 186 190 189 0.90196 0.45098 0.00000
3 190 186 187 0.90196 0.45098 0.00000
3 187 51 190 0.90196 0.45098 0.00000
3 51 187 188 0.90196 0.45098 0.00000
3 228 188 8 0.90196 0.45098 0.00000
3 191 15 189 0.90196 0.45098 0.00000
3 189 50 191 0.90196 0.45098 0.00000
3 50 189 190 0.90196 0.45098 0.00000
3 224 190 51 0.90196 0.45098 0.00000
3 4 148 191 0.90196 0.45098 0.00000
3 222 191 50 0.90196 0.45098 0.00000
3 192 5 141 0.90196 0.45098 0.00000
3 56 98 192 0.90196 0.45098 0.00000
3 192 193 56 0.90196 0.45098 0.00000
3 193 192 58 0.90196 0.45098 0.00000
3 58 194 193 0.90196 0.45098 0.00000
3 194 58 140 0.90196 0.45098 0.00000
3 195 56 193 0.90196 0.45098 0.00000
3 193 196 195 0.90196 0.45098 0.00000
3 196 193 194 0.90196 0.45098 0.00000
3 194 197 196 0.90196 0.45098 0.00000
3 197 194 59 0.90196 0.45098 0.00000
3 59 198 197 0.90196 0.45098 0.00000
3 198 59 138 0.90196 0.45098 0.00000
3 57 100 195 0.90196 0.45098 0.00000
3 195 199 57 0.90196 0.45098 0.00000
3 199 195 196 0.90196 0.45098 0.00000
3 196 200 199 0.90196 0.45098 0.00000
3 200 196 197 0.90196 0.45098 0.00000
3 197 69 200 0.90196 0.45098 0.00000
3 69 197 198 0.90196 0.45098 0.00000
3 231 198 9 0.90196 0.45098 0.00000
3 201 57 199 0.90196 0.45098 0.00000
3 199 68 201 0.90196 0.45098 0.00000
3 68 199 200 0.90196 0.45098 0.00000
3 230 200 69 0.90196 0.45098 0.00000
3 8 101 201 0.90196 0.45098 0.00000
3 228 201 68 0.90196 0.45098 0.00000
3 202 1 112 0.90196 0.45098 0.00000
3 28 212 202 0.90196 0.45098 0.00000
3 202 203 28 0.90196 0.45098 0.00000
3 203 202 24 0.90196 0.45098 0.00000
3 24 204 203 0.90196 0.45098 0.00000
3 204 24 115 0.90196 0.45098 0.00000
3 205 28 203 0.90196 0.45098 0.00000
3 203 206 205 0.90196 0.45098 0.00000
3 206 203 204 0.90196 0.45098 0.00000
3 204 207 206 0.90196 0.45098 0.00000
3 207 204 25 0.90196 0.45098 0.00000
3 25 208 207 0.90196 0.45098 0.00000
3 208 25 121 0.90196 0.45098 0.00000
3 29 214 205 0.90196 0.45098 0.00000
3 205 209 29 0.90196 0.45098 0.00000
3 209 205 206 0.90196 0.45098 0.00000
3 206 210 209 0.90196 0.45098 0.00000
3 210 206 207 0.90196 0.45098 0.00000
3 207 52 210 0.90196 0.45098 0.00000
3 52 207 208 0.90196 0.45098 0.00000
3 222 208 4 0.90196 0.45098 0.00000
3 211 29 209 0.90196 0.45098 0.00000
3 209 53 211 0.90196 0.45098 0.00000
3 53 209 210 0.90196 0.45098 0.00000
3 225 210 52 0.90196 0.45098 0.00000
3 9 218 211 0.90196 0.45098 0.00000
3 231 211 53 0.90196 0.45098 0.00000
3 212 1 202 0.90196 0.45098 0.00000
3 22 72 212 0.90196 0.45098 0.00000
3 212 213 22 0.90196 0.45098 0.00000
3 213 212 28 0.90196 0.45098 0.00000
3 28 214 213 0.90196 0.45098 0.00000
3 214 28 205 0.90196 0.45098 0.00000
3 215 22 213 0.90196 0.45098 0.00000
3 213 216 215 0.90196 0.45098 0.00000
3 216 213 214 0.90196 0.45098 0.00000
3 214 217 216 0.90196 0.45098 0.00000
3 217 214 29 0.90196 0.45098 0.00000
3 29 218 217 0.90196 0.45098 0.00000
3 218 29 211 0.90196 0.45098 0.00000
3 23 74 215 0.90196 0.45098 0.00000
3 215 219 23 0.90196 0.45098 0.00000
3 219 215 216 0.90196 0.45098 0.00000
3 216 220 219 0.90196 0.45098 0.00000
3 220 216 217 0.90196 0.45098 0.00000
3 217 45 220 0.90196 0.45098 0.00000
3 45 217 218 0.90196 0.45098 0.00000
3 138 218 9 0.90196 0.45098 0.00000
3 221 23 219 0.90196 0.45098 0.00000
3 219 44 221 0.90196 0.45098 0.00000
3 44 219 220 0.90196 0.45098 0.00000
3 134 220 45 0.90196 0.45098 0.00000
3 3 78 221 0.90196 0.45098 0.00000
3 132 221 44 0.90196 0.45098 0.00000
3 222 4 191 0.90196 0.45098 0.00000
3 52 208 222 0.90196 0.45098 0.00000
3 222 223 52 0.90196 0.45098 0.00000
3 223 222 50 0.90196 0.45098 0.00000
3 50 224 223 0.90196 0.45098 0.00000
3 224 50 190 0.90196 0.45098 0.00000
3 225 52 223 0.90196 0.45098 0.00000
3 223 226 225 0.90196 0.45098 0.00000
3 226 223 224 0.90196 0.45098 0.00000
3 224 227 226 0.90196 0.45098 0.00000
3 227 224 51 0.90196 0.45098 0.00000
3 51 228 227 0.90196 0.45098 0.00000
3 228 51 188 0.90196 0.45098 0.00000
3 53 210 225 0.90196 0.45098 0.00000
3 225 229 53 0.90196 0.45098 0.00000
3 229 225 226 0.90196 0.45098 0.00000
3 226 230 229 0.90196 0.45098 0.00000
3 230 226 227 0.90196 0.45098 0.00000
3 227 68 230 0.90196 0.45098 0.00000
3 68 227 228 0.90196 0.45098 0.00000
3 201 228 8 0.90196 0.45098 0.00000
3 231 53 229 0.90196 0.45098 0.00000
3 229 69 231 0.90196 0.45098 0.00000
3 69 229 230 0.90196 0.45098 0.00000
3 200 230 68 0.90196 0.45098 0.00000
3 9 211 231 0.90196 0.45098 0.00000
3 198 231 69 0.90196 0.45098 0.00000
3 232 0 142 0.90196 0.45098 0.00000
3 20 82 232 0.90196 0.45098 0.00000
3 232 233 20 0.90196 0.45098 0.00000
3 233 232 16 0.90196 0.45098 0.00000
3 16 234 233 0.90196 0.45098 0.00000
3 234 16 145 0.90196 0.45098 0.00000
3 235 20 233 0.90196 0.45098 0.00000
3 233 236 235 0.90196 0.45098 0.00000
3 236 233 234 0.90196 0.45098 0.00000
3 234 237 236 0.90196 0.45098 0.00000
3 237 234 17 0.90196 0.45098 0.00000
3 17 238 237 0.90196 0.45098 0.00000
3 238 17 151 0.90196 0.45098 0.00000
3 21 84 235 0.90196 0.45098 0.00000
3 235 239 21 0.90196 0.45098 0.00000
3 239 235 236 0.90196 0.45098 0.00000
3 236 240 239 0.90196 0.45098 0.00000
3 240 236 237 0.90196 0.45098 0.00000
3 237 60 240 0.90196 0.45098 0.00000
3 60 237 238 0.90196 0.45098 0.00000
3 242 238 6 0.90196 0.45098 0.00000
3 241 21 239 0.90196 0.45098 0.00000
3 239 61 241 0.90196 0.45098 0.00000
3 61 239 240 0.90196 0.45098 0.00000
3 245 240 60 0.90196 0.45098 0.00000
3 10 88 241 0.90196 0.45098 0.00000
3 251 241 61 0.90196 0.45098 0.00000
3 242 6 261 0.90196 0.45098 0.00000
3 60 238 242 0.90196 0.45098 0.00000
3 242 243 60 0.90196 0.45098 0.00000
3 243 242 62 0.90196 0.45098 0.00000
3 62 244 243 0.90196 0.45098 0.00000
3 244 62 260 0.90196 0.45098 0.00000
3 245 60 243 0.90196 0.45098 0.00000
3 243 246 245 0.90196 0.45098 0.00000
3 246 243 244 0.90196 0.45098 0.00000
3 244 247 246 0.90196 0.45098 0.00000
3 247 244 63 0.90196 0.45098 0.00000
3 63 248 247 0.90196 0.45098 0.00000
3 248 63 258 0.90196 0.45098 0.00000
3 61 240 245 0.90196 0.45098 0.00000
3 245 249 61 0.90196 0.45098 0.00000
3 249 245 246 0.90196 0.45098 0.00000
3 246 250 249 0.90196 0.45098 0.00000
3 250 246 247 0.90196 0.45098 0.00000
3 247 71 250 0.90196 0.45098 0.00000
3 71 247 248 0.90196 0.45098 0.00000
3 271 248 11 0.90196 0.45098 0.00000
3 251 61 249 0.90196 0.45098 0.00000
3 249 70 251 0.90196 0.45098 0.00000
3 70 249 250 0.90196 0.45098 0.00000
3 270 250 71 0.90196 0.45098 0.00000
3 10 241 251 0.90196 0.45098 0.00000
3 268 251 70 0.90196 0.45098 0.00000
3 252 1 72 0.90196 0.45098 0.00000
3 26 112 252 0.90196 0.45098 0.00000
3 252 253 26 0.90196 0.45098 0.00000
3 253 252 30 0.90196 0.45098 0.00000
3 30 254 253 0.90196 0.45098 0.00000
3 254 30 75 0.90196 0.45098 0.00000
3 255 26 253 0.90196 0.45098 0.00000
3 253 256 255 0.90196 0.45098 0.00000
3 256 253 254 0.90196 0.45098 0.00000
3 254 257 256 0.90196 0.45098 0.00000
3 257 254 31 0.90196 0.45098 0.00000
3 31 258 257 0.90196 0.45098 0.00000
3 258 31 81 0.90196 0.45098 0.00000
3 27 114 255 0.90196 0.45098 0.00000
3 255 259 27 0.90196 0.45098 0.00000
3 259 255 256 0.90196 0.45098 0.00000
3 256 260 259 0.90196 0.45098 0.00000
3 260 256 257 0.90196 0.45098 0.00000
3 257 63 260 0.90196 0.45098 0.00000
3 63 257 258 0.90196 0.45098 0.00000
3 248 258 11 0.90196 0.45098 0.00000
3 261 27 259 0.90196 0.45098 0.00000
3 259 62 261 0.90196 0.45098 0.00000
3 62 259 260 0.90196 0.45098 0.00000
3 244 260 63 0.90196 0.45098 0.00000
3 6 118 261 0.90196 0.45098 0.00000
3 242 261 62 0.90196 0.45098 0.00000
3 262 7 161 0.90196 0.45098 0.00000
3 66 108 262 0.90196 0.45098 0.00000
3 262 263 66 0.90196 0.45098 0.00000
3 263 262 64 0.90196 0.45098 0.00000
3 64 264 263 0.90196 0.45098 0.00000
3 264 64 160 0.90196 0.45098 0.00000
3 265 66 263 0.90196 0.45098 0.00000
3 263 266 265 0.90196 0.45098 0.00000
3 266 263 264 0.90196 0.45098 0.00000
3 264 267 266 0.90196 0.45098 0.00000
3 267 264 65 0.90196 0.45098 0.00000
3 65 268 267 0.90196 0.45098 0.00000
3 268 65 158 0.90196 0.45098 0.00000
3 67 110 265 0.90196 0.45098 0.00000
3 265 269 67 0.90196 0.45098 0.00000
3 269 265 266 0.90196 0.45098 0.00000
3 266 270 269 0.90196 0.45098 0.00000
3 270 266 267 0.90196 0.45098 0.00000
3 267 70 270 0.90196 0.45098 0.00000
3 70 267 268 0.90196 0.45098 0.00000
3 251 268 10 0.90196 0.45098 0.00000
3 271 67 269 0.90196 0.45098 0.00000
3 269 71 271 0.90196 0.45098 0.00000
3 71 269 270 0.90196 0.45098 0.00000
3 250 270 70 0.90196 0.45098 0.00000
3 11 111 271 0.90196 0.45098 0.00000
3 248 271 71 0.90196 0.45098 0.00000
1 0 0.38824 0.60000 0.30196
1 1 0.38824 0.60000 0.30196
1 2 0.38824 0.60000 0.30196
1 3 0.38824 0.60000 0.30196
1 4 0.38824 0.60000 0.30196
1 5 0.38824 0.60000 0.30196
1 6 0.38824 0.60000 0.30196
1 7 0.38824 0.60000 0.30196
1 8 0.38824 0.60000 0.30196
1 9 0.38824 0.60000 0.30196
1 10 0.38824 0.60000 0.30196
1 11 0.38824 0.60000 0.30196

View File

@ -0,0 +1,376 @@
OFF
122 252 0
0 0.5257311121191336 0.85065080835204
0 0.5257311121191336 -0.85065080835204
0 -0.5257311121191336 0.85065080835204
0 -0.5257311121191336 -0.85065080835204
0.5257311121191336 0.85065080835204 0
0.5257311121191336 -0.85065080835204 0
-0.5257311121191336 0.85065080835204 0
-0.5257311121191336 -0.85065080835204 0
0.85065080835204 0 0.5257311121191336
0.85065080835204 0 -0.5257311121191336
-0.85065080835204 0 0.5257311121191336
-0.85065080835204 0 -0.5257311121191336
2.175242402100701e-16 -1.643460219210441e-32 1
0.3090169943749475 0.8090169943749472 0.5000000000000002
-0.3090169943749475 0.8090169943749472 0.5000000000000002
0.4999999999999999 0.3090169943749474 0.8090169943749472
-0.5000000000000001 0.3090169943749475 0.8090169943749472
2.175242402100701e-16 1.643460219210441e-32 -1
0.3090169943749475 0.8090169943749472 -0.5000000000000002
-0.3090169943749475 0.8090169943749472 -0.5000000000000002
0.5 0.3090169943749473 -0.8090169943749475
-0.4999999999999999 0.3090169943749474 -0.8090169943749472
0.3090169943749473 -0.8090169943749475 0.5
-0.3090169943749475 -0.8090169943749472 0.5000000000000002
0.5 -0.3090169943749473 0.8090169943749475
-0.4999999999999999 -0.3090169943749474 0.8090169943749472
0.3090169943749473 -0.8090169943749475 -0.5
-0.3090169943749473 -0.8090169943749475 -0.5
0.5 -0.3090169943749472 -0.8090169943749475
-0.5000000000000001 -0.3090169943749475 -0.8090169943749472
0 1 4.350484804201401e-17
0.8090169943749475 0.5 0.3090169943749472
0.8090169943749472 0.4999999999999999 -0.3090169943749473
0 -1 -4.350484804201401e-17
0.8090169943749472 -0.4999999999999999 0.3090169943749473
0.8090169943749475 -0.5 -0.3090169943749472
-0.8090169943749472 0.4999999999999999 0.3090169943749473
-0.8090169943749472 0.4999999999999999 -0.3090169943749475
-0.8090169943749475 -0.5 0.3090169943749472
-0.8090169943749472 -0.4999999999999999 -0.3090169943749473
1 2.175242402100701e-16 -1.643460219210441e-32
-1 -2.175242402100701e-16 -1.643460219210441e-32
-0.1803319730021167 0.289241011911498 -0.9401170227910867
-0.35682208977309 -3.124513936890529e-17 -0.9341723589627157
-0.1803319730021166 -0.2892410119114981 -0.9401170227910867
-0.6483337612153338 -5.436311068297173e-17 -0.7613562464893677
-0.1803319730021166 0.2892410119114981 0.9401170227910867
-0.35682208977309 3.09531117213564e-17 0.9341723589627158
-0.6483337612153338 5.402340711901317e-17 0.7613562464893677
-0.1803319730021167 -0.289241011911498 0.9401170227910867
0.291783261575753 -0.5810242734872509 0.7597850497889703
0.5773502691896258 -0.5773502691896256 0.5773502691896258
0.5810242734872511 -0.7597850497889701 0.291783261575753
0.7597850497889702 -0.291783261575753 0.5810242734872511
-0.291783261575753 -0.5810242734872509 -0.7597850497889703
-0.5773502691896258 -0.5773502691896256 -0.5773502691896258
-0.5810242734872511 -0.7597850497889701 -0.291783261575753
-0.7597850497889702 -0.291783261575753 -0.5810242734872511
-2.313323858849861e-18 0.7613562464893674 -0.6483337612153339
3.124513936890529e-17 0.9341723589627158 -0.3568220897730901
-0.2892410119114981 0.9401170227910867 -0.1803319730021165
0.2892410119114981 0.9401170227910867 -0.1803319730021165
-2.313323858849861e-18 -0.7613562464893674 0.6483337612153339
3.124513936890529e-17 -0.9341723589627158 0.3568220897730901
-0.2892410119114981 -0.9401170227910867 0.1803319730021165
0.2892410119114981 -0.9401170227910867 0.1803319730021165
0.2917832615757529 -0.5810242734872509 -0.7597850497889704
0.5773502691896258 -0.5773502691896257 -0.5773502691896258
0.7597850497889701 -0.2917832615757531 -0.5810242734872512
0.5810242734872511 -0.7597850497889701 -0.291783261575753
2.313323858849861e-18 0.7613562464893674 0.6483337612153339
-3.124513936890529e-17 0.9341723589627158 0.3568220897730901
0.2892410119114981 0.9401170227910867 0.1803319730021165
-0.2892410119114981 0.9401170227910867 0.1803319730021165
-0.2917832615757529 -0.5810242734872509 0.7597850497889704
-0.5773502691896258 -0.5773502691896257 0.5773502691896258
-0.7597850497889701 -0.2917832615757531 0.5810242734872512
-0.5810242734872511 -0.7597850497889701 0.291783261575753
2.313323858849861e-18 -0.7613562464893674 -0.6483337612153339
-3.124513936890529e-17 -0.9341723589627158 -0.3568220897730901
0.2892410119114981 -0.9401170227910867 -0.1803319730021165
-0.2892410119114981 -0.9401170227910867 -0.1803319730021165
0.1803319730021167 0.289241011911498 0.9401170227910867
0.35682208977309 -3.124513936890529e-17 0.9341723589627157
0.1803319730021166 -0.2892410119114981 0.9401170227910867
0.6483337612153338 -5.436311068297173e-17 0.7613562464893677
0.2917832615757529 0.5810242734872509 0.7597850497889704
0.5773502691896258 0.5773502691896257 0.5773502691896258
0.7597850497889701 0.2917832615757531 0.5810242734872512
0.5810242734872511 0.7597850497889701 0.291783261575753
0.7613562464893677 -0.6483337612153338 5.436311068297173e-17
0.9341723589627157 -0.35682208977309 3.124513936890529e-17
0.9401170227910867 -0.1803319730021167 -0.289241011911498
0.9401170227910867 -0.1803319730021166 0.2892410119114981
0.291783261575753 0.5810242734872509 -0.7597850497889703
0.5773502691896258 0.5773502691896256 -0.5773502691896258
0.5810242734872511 0.7597850497889701 -0.291783261575753
0.7597850497889702 0.291783261575753 -0.5810242734872511
0.1803319730021166 0.2892410119114981 -0.9401170227910867
0.35682208977309 3.09531117213564e-17 -0.9341723589627158
0.6483337612153338 5.402340711901317e-17 -0.7613562464893677
0.1803319730021167 -0.289241011911498 -0.9401170227910867
0.7613562464893677 0.6483337612153338 -5.436311068297173e-17
0.9341723589627157 0.35682208977309 -3.124513936890529e-17
0.9401170227910867 0.1803319730021167 0.289241011911498
0.9401170227910867 0.1803319730021166 -0.2892410119114981
-0.291783261575753 0.5810242734872509 0.7597850497889703
-0.5773502691896258 0.5773502691896256 0.5773502691896258
-0.5810242734872511 0.7597850497889701 0.291783261575753
-0.7597850497889702 0.291783261575753 0.5810242734872511
-0.7613562464893677 0.6483337612153338 5.436311068297173e-17
-0.9341723589627157 0.35682208977309 3.124513936890529e-17
-0.9401170227910867 0.1803319730021167 -0.289241011911498
-0.9401170227910867 0.1803319730021166 0.2892410119114981
-0.2917832615757529 0.5810242734872509 -0.7597850497889704
-0.5773502691896258 0.5773502691896257 -0.5773502691896258
-0.7597850497889701 0.2917832615757531 -0.5810242734872512
-0.5810242734872511 0.7597850497889701 -0.291783261575753
-0.7613562464893677 -0.6483337612153338 -5.436311068297173e-17
-0.9341723589627157 -0.35682208977309 -3.124513936890529e-17
-0.9401170227910867 -0.1803319730021167 0.289241011911498
-0.9401170227910867 -0.1803319730021166 -0.2892410119114981
3 42 1 98 0.90196 0.45098 0.00000
3 21 114 42 0.90196 0.45098 0.00000
3 42 43 21 0.90196 0.45098 0.00000
3 43 42 17 0.90196 0.45098 0.00000
3 17 44 43 0.90196 0.45098 0.00000
3 44 17 101 0.90196 0.45098 0.00000
3 45 21 43 0.90196 0.45098 0.00000
3 43 29 45 0.90196 0.45098 0.00000
3 29 43 44 0.90196 0.45098 0.00000
3 54 44 3 0.90196 0.45098 0.00000
3 11 116 45 0.90196 0.45098 0.00000
3 57 45 29 0.90196 0.45098 0.00000
3 46 0 106 0.90196 0.45098 0.00000
3 12 82 46 0.90196 0.45098 0.00000
3 46 47 12 0.90196 0.45098 0.00000
3 47 46 16 0.90196 0.45098 0.00000
3 16 48 47 0.90196 0.45098 0.00000
3 48 16 109 0.90196 0.45098 0.00000
3 49 12 47 0.90196 0.45098 0.00000
3 47 25 49 0.90196 0.45098 0.00000
3 25 47 48 0.90196 0.45098 0.00000
3 76 48 10 0.90196 0.45098 0.00000
3 2 84 49 0.90196 0.45098 0.00000
3 74 49 25 0.90196 0.45098 0.00000
3 50 2 62 0.90196 0.45098 0.00000
3 24 84 50 0.90196 0.45098 0.00000
3 50 51 24 0.90196 0.45098 0.00000
3 51 50 22 0.90196 0.45098 0.00000
3 22 52 51 0.90196 0.45098 0.00000
3 52 22 65 0.90196 0.45098 0.00000
3 53 24 51 0.90196 0.45098 0.00000
3 51 34 53 0.90196 0.45098 0.00000
3 34 51 52 0.90196 0.45098 0.00000
3 90 52 5 0.90196 0.45098 0.00000
3 8 85 53 0.90196 0.45098 0.00000
3 93 53 34 0.90196 0.45098 0.00000
3 54 3 78 0.90196 0.45098 0.00000
3 29 44 54 0.90196 0.45098 0.00000
3 54 55 29 0.90196 0.45098 0.00000
3 55 54 27 0.90196 0.45098 0.00000
3 27 56 55 0.90196 0.45098 0.00000
3 56 27 81 0.90196 0.45098 0.00000
3 57 29 55 0.90196 0.45098 0.00000
3 55 39 57 0.90196 0.45098 0.00000
3 39 55 56 0.90196 0.45098 0.00000
3 118 56 7 0.90196 0.45098 0.00000
3 11 45 57 0.90196 0.45098 0.00000
3 121 57 39 0.90196 0.45098 0.00000
3 58 1 114 0.90196 0.45098 0.00000
3 18 94 58 0.90196 0.45098 0.00000
3 58 59 18 0.90196 0.45098 0.00000
3 59 58 19 0.90196 0.45098 0.00000
3 19 60 59 0.90196 0.45098 0.00000
3 60 19 117 0.90196 0.45098 0.00000
3 61 18 59 0.90196 0.45098 0.00000
3 59 30 61 0.90196 0.45098 0.00000
3 30 59 60 0.90196 0.45098 0.00000
3 73 60 6 0.90196 0.45098 0.00000
3 4 96 61 0.90196 0.45098 0.00000
3 72 61 30 0.90196 0.45098 0.00000
3 62 2 74 0.90196 0.45098 0.00000
3 22 50 62 0.90196 0.45098 0.00000
3 62 63 22 0.90196 0.45098 0.00000
3 63 62 23 0.90196 0.45098 0.00000
3 23 64 63 0.90196 0.45098 0.00000
3 64 23 77 0.90196 0.45098 0.00000
3 65 22 63 0.90196 0.45098 0.00000
3 63 33 65 0.90196 0.45098 0.00000
3 33 63 64 0.90196 0.45098 0.00000
3 81 64 7 0.90196 0.45098 0.00000
3 5 52 65 0.90196 0.45098 0.00000
3 80 65 33 0.90196 0.45098 0.00000
3 66 3 101 0.90196 0.45098 0.00000
3 26 78 66 0.90196 0.45098 0.00000
3 66 67 26 0.90196 0.45098 0.00000
3 67 66 28 0.90196 0.45098 0.00000
3 28 68 67 0.90196 0.45098 0.00000
3 68 28 100 0.90196 0.45098 0.00000
3 69 26 67 0.90196 0.45098 0.00000
3 67 35 69 0.90196 0.45098 0.00000
3 35 67 68 0.90196 0.45098 0.00000
3 92 68 9 0.90196 0.45098 0.00000
3 5 80 69 0.90196 0.45098 0.00000
3 90 69 35 0.90196 0.45098 0.00000
3 70 0 86 0.90196 0.45098 0.00000
3 14 106 70 0.90196 0.45098 0.00000
3 70 71 14 0.90196 0.45098 0.00000
3 71 70 13 0.90196 0.45098 0.00000
3 13 72 71 0.90196 0.45098 0.00000
3 72 13 89 0.90196 0.45098 0.00000
3 73 14 71 0.90196 0.45098 0.00000
3 71 30 73 0.90196 0.45098 0.00000
3 30 71 72 0.90196 0.45098 0.00000
3 61 72 4 0.90196 0.45098 0.00000
3 6 108 73 0.90196 0.45098 0.00000
3 60 73 30 0.90196 0.45098 0.00000
3 74 2 49 0.90196 0.45098 0.00000
3 23 62 74 0.90196 0.45098 0.00000
3 74 75 23 0.90196 0.45098 0.00000
3 75 74 25 0.90196 0.45098 0.00000
3 25 76 75 0.90196 0.45098 0.00000
3 76 25 48 0.90196 0.45098 0.00000
3 77 23 75 0.90196 0.45098 0.00000
3 75 38 77 0.90196 0.45098 0.00000
3 38 75 76 0.90196 0.45098 0.00000
3 120 76 10 0.90196 0.45098 0.00000
3 7 64 77 0.90196 0.45098 0.00000
3 118 77 38 0.90196 0.45098 0.00000
3 78 3 66 0.90196 0.45098 0.00000
3 27 54 78 0.90196 0.45098 0.00000
3 78 79 27 0.90196 0.45098 0.00000
3 79 78 26 0.90196 0.45098 0.00000
3 26 80 79 0.90196 0.45098 0.00000
3 80 26 69 0.90196 0.45098 0.00000
3 81 27 79 0.90196 0.45098 0.00000
3 79 33 81 0.90196 0.45098 0.00000
3 33 79 80 0.90196 0.45098 0.00000
3 65 80 5 0.90196 0.45098 0.00000
3 7 56 81 0.90196 0.45098 0.00000
3 64 81 33 0.90196 0.45098 0.00000
3 82 0 46 0.90196 0.45098 0.00000
3 15 86 82 0.90196 0.45098 0.00000
3 82 83 15 0.90196 0.45098 0.00000
3 83 82 12 0.90196 0.45098 0.00000
3 12 84 83 0.90196 0.45098 0.00000
3 84 12 49 0.90196 0.45098 0.00000
3 85 15 83 0.90196 0.45098 0.00000
3 83 24 85 0.90196 0.45098 0.00000
3 24 83 84 0.90196 0.45098 0.00000
3 50 84 2 0.90196 0.45098 0.00000
3 8 88 85 0.90196 0.45098 0.00000
3 53 85 24 0.90196 0.45098 0.00000
3 86 0 82 0.90196 0.45098 0.00000
3 13 70 86 0.90196 0.45098 0.00000
3 86 87 13 0.90196 0.45098 0.00000
3 87 86 15 0.90196 0.45098 0.00000
3 15 88 87 0.90196 0.45098 0.00000
3 88 15 85 0.90196 0.45098 0.00000
3 89 13 87 0.90196 0.45098 0.00000
3 87 31 89 0.90196 0.45098 0.00000
3 31 87 88 0.90196 0.45098 0.00000
3 104 88 8 0.90196 0.45098 0.00000
3 4 72 89 0.90196 0.45098 0.00000
3 102 89 31 0.90196 0.45098 0.00000
3 90 5 69 0.90196 0.45098 0.00000
3 34 52 90 0.90196 0.45098 0.00000
3 90 91 34 0.90196 0.45098 0.00000
3 91 90 35 0.90196 0.45098 0.00000
3 35 92 91 0.90196 0.45098 0.00000
3 92 35 68 0.90196 0.45098 0.00000
3 93 34 91 0.90196 0.45098 0.00000
3 91 40 93 0.90196 0.45098 0.00000
3 40 91 92 0.90196 0.45098 0.00000
3 105 92 9 0.90196 0.45098 0.00000
3 8 53 93 0.90196 0.45098 0.00000
3 104 93 40 0.90196 0.45098 0.00000
3 94 1 58 0.90196 0.45098 0.00000
3 20 98 94 0.90196 0.45098 0.00000
3 94 95 20 0.90196 0.45098 0.00000
3 95 94 18 0.90196 0.45098 0.00000
3 18 96 95 0.90196 0.45098 0.00000
3 96 18 61 0.90196 0.45098 0.00000
3 97 20 95 0.90196 0.45098 0.00000
3 95 32 97 0.90196 0.45098 0.00000
3 32 95 96 0.90196 0.45098 0.00000
3 102 96 4 0.90196 0.45098 0.00000
3 9 100 97 0.90196 0.45098 0.00000
3 105 97 32 0.90196 0.45098 0.00000
3 98 1 94 0.90196 0.45098 0.00000
3 17 42 98 0.90196 0.45098 0.00000
3 98 99 17 0.90196 0.45098 0.00000
3 99 98 20 0.90196 0.45098 0.00000
3 20 100 99 0.90196 0.45098 0.00000
3 100 20 97 0.90196 0.45098 0.00000
3 101 17 99 0.90196 0.45098 0.00000
3 99 28 101 0.90196 0.45098 0.00000
3 28 99 100 0.90196 0.45098 0.00000
3 68 100 9 0.90196 0.45098 0.00000
3 3 44 101 0.90196 0.45098 0.00000
3 66 101 28 0.90196 0.45098 0.00000
3 102 4 89 0.90196 0.45098 0.00000
3 32 96 102 0.90196 0.45098 0.00000
3 102 103 32 0.90196 0.45098 0.00000
3 103 102 31 0.90196 0.45098 0.00000
3 31 104 103 0.90196 0.45098 0.00000
3 104 31 88 0.90196 0.45098 0.00000
3 105 32 103 0.90196 0.45098 0.00000
3 103 40 105 0.90196 0.45098 0.00000
3 40 103 104 0.90196 0.45098 0.00000
3 93 104 8 0.90196 0.45098 0.00000
3 9 97 105 0.90196 0.45098 0.00000
3 92 105 40 0.90196 0.45098 0.00000
3 106 0 70 0.90196 0.45098 0.00000
3 16 46 106 0.90196 0.45098 0.00000
3 106 107 16 0.90196 0.45098 0.00000
3 107 106 14 0.90196 0.45098 0.00000
3 14 108 107 0.90196 0.45098 0.00000
3 108 14 73 0.90196 0.45098 0.00000
3 109 16 107 0.90196 0.45098 0.00000
3 107 36 109 0.90196 0.45098 0.00000
3 36 107 108 0.90196 0.45098 0.00000
3 110 108 6 0.90196 0.45098 0.00000
3 10 48 109 0.90196 0.45098 0.00000
3 113 109 36 0.90196 0.45098 0.00000
3 110 6 117 0.90196 0.45098 0.00000
3 36 108 110 0.90196 0.45098 0.00000
3 110 111 36 0.90196 0.45098 0.00000
3 111 110 37 0.90196 0.45098 0.00000
3 37 112 111 0.90196 0.45098 0.00000
3 112 37 116 0.90196 0.45098 0.00000
3 113 36 111 0.90196 0.45098 0.00000
3 111 41 113 0.90196 0.45098 0.00000
3 41 111 112 0.90196 0.45098 0.00000
3 121 112 11 0.90196 0.45098 0.00000
3 10 109 113 0.90196 0.45098 0.00000
3 120 113 41 0.90196 0.45098 0.00000
3 114 1 42 0.90196 0.45098 0.00000
3 19 58 114 0.90196 0.45098 0.00000
3 114 115 19 0.90196 0.45098 0.00000
3 115 114 21 0.90196 0.45098 0.00000
3 21 116 115 0.90196 0.45098 0.00000
3 116 21 45 0.90196 0.45098 0.00000
3 117 19 115 0.90196 0.45098 0.00000
3 115 37 117 0.90196 0.45098 0.00000
3 37 115 116 0.90196 0.45098 0.00000
3 112 116 11 0.90196 0.45098 0.00000
3 6 60 117 0.90196 0.45098 0.00000
3 110 117 37 0.90196 0.45098 0.00000
3 118 7 77 0.90196 0.45098 0.00000
3 39 56 118 0.90196 0.45098 0.00000
3 118 119 39 0.90196 0.45098 0.00000
3 119 118 38 0.90196 0.45098 0.00000
3 38 120 119 0.90196 0.45098 0.00000
3 120 38 76 0.90196 0.45098 0.00000
3 121 39 119 0.90196 0.45098 0.00000
3 119 41 121 0.90196 0.45098 0.00000
3 41 119 120 0.90196 0.45098 0.00000
3 113 120 10 0.90196 0.45098 0.00000
3 11 57 121 0.90196 0.45098 0.00000
3 112 121 41 0.90196 0.45098 0.00000
1 0 0.38824 0.60000 0.30196
1 1 0.38824 0.60000 0.30196
1 2 0.38824 0.60000 0.30196
1 3 0.38824 0.60000 0.30196
1 4 0.38824 0.60000 0.30196
1 5 0.38824 0.60000 0.30196
1 6 0.38824 0.60000 0.30196
1 7 0.38824 0.60000 0.30196
1 8 0.38824 0.60000 0.30196
1 9 0.38824 0.60000 0.30196
1 10 0.38824 0.60000 0.30196
1 11 0.38824 0.60000 0.30196

View File

@ -0,0 +1,205 @@
"""Tests the generated graphs are well-formed."""
# Standard Library
from contextlib import redirect_stdout
from io import StringIO
from math import isclose
# Testing
import unittest
# Typing
from typing import cast
# Hypothesis testing
from hypothesis import given
import hypothesis.strategies as st
# Scientific (testing)
import numpy as np
import numpy.testing
# Own geography
from pyrate.plan.geometry.geospatial import MEAN_EARTH_CIRCUMFERENCE
from pyrate.plan.geometry.helpers import haversine_numpy
# Module under test
from pyrate.plan.graph.generate import angular_distance_for
from pyrate.plan.graph.generate import create_earth_graph
from pyrate.plan.graph.generate import great_circle_distance_distance_for
from pyrate.plan.graph.generate import min_required_frequency
EXAMPLE_DISTANCES_KILOMETERS = [100000, 100000.0, 5000, 250] # smaller values take too long
class TestGridGeneration(unittest.TestCase):
"""Tests that a grid can be created and pruned."""
@staticmethod
def _calculate_distances(latitudes: np.ndarray, longitudes: np.ndarray, edges: np.ndarray) -> np.ndarray:
"""Calculates the distance of all edges. The `edges` index into the coordinate arrays."""
entries = [
(latitudes[node_1], longitudes[node_1], latitudes[node_2], longitudes[node_2])
for node_1, node_2 in edges
]
return haversine_numpy(*np.transpose(entries))
def test_create_earth_grid(self) -> None:
"""Ensures that the generated earth grids are formed correctly."""
for distance_km in EXAMPLE_DISTANCES_KILOMETERS:
with self.subTest(f"Test with distance {distance_km} km"):
distance = distance_km * 1000
# create a grid
graph = create_earth_graph(min_required_frequency(distance, in_meters=True))
self.assertIsNotNone(graph.node_radius)
actual_distance: float = cast(float, graph.node_radius) * 2
# the actual_distance must be a upper-bounded by he requested distance
self.assertLessEqual(actual_distance, distance)
self.assertLessEqual(actual_distance, MEAN_EARTH_CIRCUMFERENCE / 2)
# the shapes of the returned arrays must match
self.assertEqual(
graph.latitudes_radians.shape,
graph.longitudes_radians.shape,
"latitude and longitude must have the same shape",
)
self.assertEqual(
graph.latitudes_degrees.shape,
graph.longitudes_degrees.shape,
"latitude and longitude must have the same shape",
)
self.assertEqual(
graph.latitudes_radians.shape,
graph.longitudes_degrees.shape,
"radians and degrees must have the same shape",
)
self.assertGreaterEqual(len(graph), 12) # as it is based on slicing an icosahedron
# the edges must be valid indices into the edges
self.assertTrue(
np.all(graph.edges[:, :] >= 0) and np.all(graph.edges[:, :] < len(graph)),
"some edges reference non-existent points",
)
# check the actual coordinate value
if (
np.any(graph.latitudes_radians < -np.pi / 2)
or np.any(graph.longitudes_radians < -np.pi)
or np.any(graph.latitudes_radians >= +np.pi / 2)
or np.any(graph.longitudes_radians >= +np.pi)
):
print(
"latitude < min / 2:",
np.compress(graph.latitudes_radians < -np.pi / 2, graph.latitudes_radians),
)
print(
"longitude < min:",
np.compress(graph.longitudes_radians < -np.pi, graph.longitudes_radians),
)
print(
"latitude >= max / 2:",
np.compress(graph.latitudes_radians >= +np.pi / 2, graph.latitudes_radians),
)
print(
"longitude >= max:",
np.compress(graph.longitudes_radians >= +np.pi, graph.longitudes_radians),
)
self.fail("some points are outside of the allowed range")
# check the distances along the edges
distances = TestGridGeneration._calculate_distances(
graph.latitudes_radians, graph.longitudes_radians, graph.edges
)
numpy.testing.assert_allclose(distances, actual_distance, atol=10, rtol=0.2)
mean = np.mean(distances)
self.assertTrue(isclose(mean, actual_distance, rel_tol=0.1, abs_tol=10.0))
standard_deviation = np.std(distances)
self.assertLessEqual(standard_deviation / mean, 0.075)
def test_print_status(self) -> None:
"""This tests that logging being enabled actually logs something and does not crash."""
stdout_logging = StringIO()
with redirect_stdout(stdout_logging):
create_earth_graph(6, print_status=True)
logged_lines = list(stdout_logging.getvalue().splitlines())
self.assertEqual(len(logged_lines), 6, "we expect 6 lines of messages")
def test_find_neighbors(self) -> None:
"""Tests that result of the neighbor search is correct."""
for distance_km in EXAMPLE_DISTANCES_KILOMETERS:
with self.subTest(f"Test with distance {distance_km} km"):
# create a grid & determine neighbors
graph = create_earth_graph(min_required_frequency(distance_km * 1000, in_meters=True))
neighbors = graph.neighbors
count_per_node = np.count_nonzero(neighbors >= 0, axis=1)
# check the resulting number of entries
self.assertEqual(
np.sum(count_per_node),
graph.edges.shape[0] * 2,
"each edge must generate two entries in the neighbor table",
)
self.assertEqual(
np.count_nonzero(count_per_node == 5),
12,
"exactly twelve nodes must have exactly five neighbors "
"(the corners of the original icosahedron)",
)
self.assertEqual(
np.count_nonzero(count_per_node == 6),
len(graph) - 12,
"all but twelve nodes must have exactly six neighbors",
)
# check the range of values
valid_index = np.logical_and(neighbors >= 0, neighbors < len(graph))
self.assertTrue(
np.all(np.logical_xor(neighbors == -1, valid_index)),
"any value i may either be -1 (=null) or a valid index with 0 <= i < num_nodes",
)
class TestHelperMethods(unittest.TestCase):
"""Tests that the helpers (e.g. for computing minimum required frequencies) work correctly."""
@given(st.floats(min_value=1e-6, allow_infinity=False, allow_nan=False), st.booleans())
def test_right_order_of_magnitude(self, desired_distance: float, in_meters: bool) -> None:
"""Asserts that commuting a frequency and converting it to units is correct w.r.t. to each other."""
frequency = min_required_frequency(desired_distance, in_meters)
if in_meters:
actual_distance = great_circle_distance_distance_for(frequency)
else:
actual_distance = angular_distance_for(frequency)
self.assertLessEqual(actual_distance, desired_distance)
if frequency > 1:
if in_meters:
actual_distance_one_rougher = great_circle_distance_distance_for(frequency - 1)
else:
actual_distance_one_rougher = angular_distance_for(frequency - 1)
self.assertGreaterEqual(actual_distance_one_rougher, desired_distance)
def test_specific_values(self) -> None:
"""Asserts that commuting a frequency works correct for specific hand-chosen values."""
# Taken from the implementation:
# The approximate angle between two edges on an icosahedron, in radians, about 63.4°
alpha = 1.1071487
# Contains pairs: (angular distance in radians, frequency)
table = [
(alpha + 1e-6, 1),
(alpha - 1e-9, 2),
(alpha / 9000.005, 9001),
]
for desired_angular_distance, desired_frequency in table:
computed_frequency = min_required_frequency(desired_angular_distance, in_meters=False)
self.assertEqual(desired_frequency, computed_frequency)

View File

@ -0,0 +1,56 @@
"""Tests the Aptiprism OFF file handler."""
# Standard library
import os.path
# Testing
import unittest
# Scientific
import numpy as np
# Module under test
from pyrate.plan.graph.generate import _parse_off_file
TEST_FILES_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "example_files/"))
TEST_FILES = [
os.path.join(TEST_FILES_DIR, "geodestic_file_1.off"),
os.path.join(TEST_FILES_DIR, "geodestic_file_2.off"),
os.path.join(TEST_FILES_DIR, "geodesic_-M_s_-c_2_-f_2_ico.off"),
]
class TestOffHandler(unittest.TestCase):
"""Tests the Aptiprism OFF file handler using some examples."""
def test_with_example_files(self):
"""Tests the Aptiprism OFF file handler using three example files."""
for test_file in TEST_FILES:
with self.subTest(f'Test file "{test_file}"'):
# test that it does not crash
with open(test_file, "r", encoding="utf-8") as myfile:
source = myfile.read()
latitudes, longitudes, edges = _parse_off_file(source)
if "geodesic_-M_s_-c_2_-f_2_ico" in test_file:
self.assertEqual(
len(latitudes), 122, f"wrong total number of nodes: {len(latitudes)} instead of 122"
)
self.assertEqual(
edges.shape[0], 360, f"wrong total number of edges: {edges.shape[0]} instead of 360"
)
# the shapes of the returned arrays must match
self.assertEqual(
latitudes.shape, longitudes.shape, "latitude and longitude must have the same shape"
)
self.assertGreater(len(latitudes), 0, "no points found")
# the edges must be valid indices into the edges
self.assertTrue(
np.all(edges[:, :] >= 0) and np.all(edges[:, :] < len(latitudes)),
"some edges reference non-existent points",
)

View File

@ -0,0 +1,166 @@
"""Asserts correct behaviour of the geo-referenced graph navigation.
See Also:
tests/common/raster_datasets/test_transformers_concrete.py
"""
# Standard library
from copy import deepcopy
import os.path
from tempfile import TemporaryDirectory
from unittest import TestCase
# Scientific
import numpy
from numpy import arange
from numpy import array
from numpy import empty
from numpy.testing import assert_array_equal
from pandas import DataFrame
# Graph generation / Module under test
from pyrate.common.raster_datasets import transformers_concrete
from pyrate.plan.graph import create_earth_graph
from pyrate.plan.graph import GeoNavigationGraph
from pyrate.plan.graph import min_required_frequency
# CI/Testing helpers
from ... import _open_test_geo_dataset
from .generate.test_graph_generation import EXAMPLE_DISTANCES_KILOMETERS
class TestGeoNavigationGraph(TestCase):
"""Tests properties specific to :class:`pyrate.plan.graph.GeoNavigationGraph`."""
def test_create_invalid_duplicate_argument_nodes(self) -> None:
"""Tests supplying nodes to from_coordinates_radians/from_coordinates_degrees raises an Exception."""
for function in [
GeoNavigationGraph.from_coordinates_degrees,
GeoNavigationGraph.from_coordinates_radians,
]:
with self.subTest(msg=f"function {str(function)}"):
with self.assertRaises(Exception): # noqa: H202
function( # type: ignore
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), nodes=DataFrame()
)
def test_node_radius_constructor(self) -> None:
"""Tests that only invalid inputs to node_radius raise exceptions."""
GeoNavigationGraph.from_coordinates_degrees(
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=0
)
GeoNavigationGraph.from_coordinates_degrees(
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=100_000
)
with self.assertRaises(Exception): # noqa: H202
GeoNavigationGraph.from_coordinates_degrees(
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=-1e-9
)
def test_set_node_properties(self) -> None:
"""Tests that passing ``node_properties`` works."""
graph = GeoNavigationGraph.from_coordinates_radians(
latitudes=array([42]),
longitudes=array([21]),
edges=empty((0, 2)),
node_radius=100,
node_properties=DataFrame(data={"col1": [99], "col2": ["text"]}),
)
self.assertEqual(graph.node_radius, 100)
assert_array_equal(graph.node_properties["col1"], [99])
assert_array_equal(graph.node_properties["col2"], ["text"])
def test_read_write(self) -> None:
"""Tests that a *geo* navigation graph can be serialized and deserialized again."""
latitudes = array([49.8725144])
longitudes = array([8.6528707])
edges = empty((0, 2))
# `graph.neighbors` is cached, so we want to try it with and without the cached neighbors being set
for set_neighbors in [True, False]:
with self.subTest(f"neighbors set = {set_neighbors}"):
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes, longitudes, edges=edges, max_neighbors=42, node_radius=1000
)
if set_neighbors:
_ = graph.neighbors
with TemporaryDirectory() as directory:
path = os.path.join(directory, "some_file.hdf5")
graph.to_disk(path)
new_graph = GeoNavigationGraph.from_disk(path)
self.assertEqual(graph, new_graph)
assert_array_equal(new_graph.neighbors, graph.neighbors)
class TestNavigationGraphPruningGeo(TestCase):
"""Tests that navigation graphs can be pruned by testing it with earth graphs."""
def test_pruning_artificial(self) -> None:
"""Tests that pruning half of the points works as expected."""
for distance_km in EXAMPLE_DISTANCES_KILOMETERS:
with self.subTest(f"Test with distance {distance_km} km"):
# create a grid
graph = create_earth_graph(min_required_frequency(distance_km * 1000, in_meters=True))
# keep all nodes at even latitudes
keep_condition = arange(0, len(graph)) % 2 == 0
pruned_graph = deepcopy(graph)
pruned_graph.prune_nodes(keep_condition)
self.assertGreater(len(pruned_graph), 0, "some node must remain")
# test the reduction ratio
delta_nodes = len(pruned_graph) / len(graph)
delta_edges = pruned_graph.num_edges / graph.num_edges
self.assertAlmostEqual(delta_nodes, 0.5, msg="suspicious node count reduction")
# about a fifth of all edges should be removed since each of the removed nodes removed five
# edges
self.assertAlmostEqual(delta_edges, 1 / 5, delta=0.15, msg="suspicious edge count reduction")
# test the values in the edges, since they were rewritten as they point to new indices
self.assertTrue(numpy.all(pruned_graph.edges[:, :] >= 0), "indices must be non-negative")
self.assertTrue(
numpy.all(pruned_graph.edges[:, :] < len(pruned_graph)),
"some filtered edges reference (now) non-existent points",
)
def test_pruning_depth(self) -> None:
"""Supplements :meth`~test_pruning_artificial` by a real-world application.
Only checks application-specific properties and not, for example, the general shapes of the result.
"""
# create a grid
distance_meters = 500_000
graph = create_earth_graph(min_required_frequency(distance_meters, in_meters=True))
# fetch properties
mode = transformers_concrete.BathymetricTransformer.Modes.AVERAGE_DEPTH
graph.append_property(transformers_concrete.BathymetricTransformer(_open_test_geo_dataset(), [mode]))
# keep all nodes that are below sea level
keep_condition = (graph.node_properties[mode.column_name] < 0.0).to_numpy()
# Remove the now useless property
graph.clear_node_properties()
# perform pruning
pruned_graph = deepcopy(graph)
pruned_graph.prune_nodes(keep_condition)
# test the reduction ratio
delta_nodes = len(pruned_graph) / len(graph)
delta_edges = pruned_graph.num_edges / graph.num_edges
earth_fraction_water = 0.708 # see https://en.wikipedia.org/wiki/World_Ocean
# although we go by topography and not water coverage, this should still be fairly correct
self.assertAlmostEqual(
delta_nodes, earth_fraction_water, delta=0.1, msg="suspicious node count reduction"
)
self.assertAlmostEqual(
delta_edges, earth_fraction_water, delta=0.1, msg="suspicious edge count reduction"
)

View File

@ -0,0 +1,120 @@
"""Asserts correct behaviour of the base classes for graph navigation.
See Also:
tests/common/raster_datasets/test_transformers_concrete.py
"""
# Standard library
from copy import deepcopy
import os.path
from tempfile import TemporaryDirectory
from unittest import TestCase
# Scientific
from numpy import array
from numpy import empty
from numpy import full
from numpy.testing import assert_array_equal
from pandas import DataFrame
from pandas.testing import assert_frame_equal
# Module under test
from pyrate.plan.graph import NavigationGraph
# Some examples:
_NODES = DataFrame(data={"property_1": [1, 2, 3], "property_2": [10, 20, 30]})
_EDGES = array([[0, 1], [1, 2]])
_NEIGHBORS = array([[1, -1], [0, 2], [1, -1]])
class TestNavigationGraph(TestCase):
"""Tests the very basic functionality like initialization, (de)serialization and finding neighbors."""
def test_empty(self) -> None:
"""Tests that a new instance can be created with and without neighbors."""
graph = NavigationGraph(DataFrame(), empty((0, 2)))
self.assertEqual(len(graph), 0)
self.assertEqual(graph.num_edges, 0)
# check that the correct neighbor table is returned
self.assertEqual(graph.neighbors.shape, (0, 0))
def test_create(self) -> None:
"""Tests that a new instance can be created with and without neighbors."""
for given_neighbors in [_NEIGHBORS, None]:
with self.subTest(f"neighbors given = {given_neighbors is not None}"):
graph = NavigationGraph(_NODES, _EDGES, given_neighbors)
assert_array_equal(graph.neighbors, _NEIGHBORS)
# repeated queries should return the same neighbors
assert_array_equal(graph.neighbors, graph.neighbors)
def test_read_write(self) -> None:
"""Tests that a navigation graph can be serialized and deserialized again."""
# `graph.neighbors` is cached, so we want to try it with and without the cached neighbors being set
for set_neighbors in [True, False]:
with self.subTest(f"neighbors set = {set_neighbors}"):
graph = NavigationGraph(_NODES, _EDGES, max_neighbors=42)
if set_neighbors:
_ = graph.neighbors
with TemporaryDirectory() as directory:
path = os.path.join(directory, "some_file.hdf5")
graph.to_disk(path)
new_graph = NavigationGraph.from_disk(path)
self.assertEqual(graph, new_graph)
assert_array_equal(new_graph.neighbors, _NEIGHBORS)
def test_max_neighbors_constructor(self) -> None:
"""Tests that only invalid inputs to max_neighbors raise exceptions."""
NavigationGraph(DataFrame(), empty((0, 2)), max_neighbors=0)
NavigationGraph(DataFrame(), empty((0, 2)), max_neighbors=10)
with self.assertRaises(Exception): # noqa: H202
NavigationGraph(DataFrame(), empty((0, 2)), max_neighbors=-2)
class TestNavigationGraphPruningArtificial(TestCase):
"""Tests that simple toy navigation graphs can be pruned."""
def test_pruning_no_nodes(self) -> None:
"""Tests that pruning no nodes works."""
old_graph = NavigationGraph(_NODES, _EDGES, _NEIGHBORS)
pruned_graph = deepcopy(old_graph)
retain_all = full((len(_NODES),), True)
pruned_graph.prune_nodes(retain_all)
self.assertEqual(old_graph, pruned_graph)
def test_pruning_all(self) -> None:
"""Tests that pruning all nodes works."""
old_graph = NavigationGraph(_NODES, _EDGES, _NEIGHBORS)
pruned_graph = deepcopy(old_graph)
retain_all = full((len(_NODES),), False)
pruned_graph.prune_nodes(retain_all)
self.assertNotEqual(old_graph, pruned_graph)
self.assertEqual(len(pruned_graph.nodes), 0)
self.assertEqual(len(pruned_graph.nodes.columns), 2, "the properties must be retained")
self.assertEqual(pruned_graph.edges.shape, (0, 2))
self.assertEqual(pruned_graph.neighbors.shape, (0, 0))
def test_pruning_very_simple(self) -> None:
"""Tests that pruning some nodes works as expected."""
old_graph = NavigationGraph(_NODES, _EDGES, _NEIGHBORS)
pruned_graph = deepcopy(old_graph)
keep_condition = array([True, True, False]) # only prune the last node
pruned_graph.prune_nodes(keep_condition)
self.assertNotEqual(old_graph, pruned_graph)
assert_frame_equal(pruned_graph.nodes, _NODES[:2])
assert_array_equal(pruned_graph.edges, _EDGES[:1])
assert_array_equal(pruned_graph.neighbors, _NEIGHBORS[:2, :1])