1
0

Removed the subdir.

This commit is contained in:
2022-07-20 15:01:07 +02:00
parent aee5e8bbd9
commit e03f1c8be8
234 changed files with 21562 additions and 456 deletions

View File

@ -0,0 +1,6 @@
"""Tests the raster datasets.
The test file ``Earth2014.TBI2014.30min.geod.geo.tif`` is the *Earth 2014* dataset, exported from the
`data repository
<https://gitlab.sailingteam.hg.tu-darmstadt.de/informatik/data/-/tree/master/topography/earth2014>`__.
"""

View File

@ -0,0 +1,201 @@
"""Tests :class:`pyrate.common.raster_datasets.geo_datasets.DataSetAccess`."""
# Standard library
from math import degrees
from math import pi
from math import radians
# Generic testing
from unittest import TestCase
# Geometry
from rasterio.windows import intersect
# Hypothesis testing
from hypothesis import given
import hypothesis.strategies as st
# Numeric testing
from numpy import rad2deg
from numpy.testing import assert_array_almost_equal
# Own Geometry
from pyrate.plan.geometry.helpers import meters2rad
from pyrate.plan.geometry.helpers import rad2meters
from pyrate.plan.geometry import PolarLocation
# Test environment helper
from ... import _open_test_geo_dataset
class TestGeoDataset(TestCase):
"""Ensure that the :class:`pyrate.plan.graph.generate.geo_datasets.DataSetAccess` works correctly.
Uses the *Earth2014* dataset.
"""
#: The resolution of the dataset in arc-minutes
DATASET_RESOLUTION = 30
#: The maximal distance of two data points in the dataset in degrees
MAX_POINT_DISTANCE_DEG = DATASET_RESOLUTION / 60
#: The maximal distance of two data points in the dataset in meters (at the equator)
MAX_POINT_DISTANCE = rad2meters(radians(MAX_POINT_DISTANCE_DEG))
# Handle the context manager, see https://stackoverflow.com/a/11180583/3753684
def run(self, result=None) -> None:
with _open_test_geo_dataset() as dataset:
self.dataset = dataset # pylint: disable=attribute-defined-outside-init
super().run(result)
@given(
st.floats(min_value=-pi / 2 * 0.75, max_value=+pi / 2 * 0.75),
st.floats(min_value=-pi * 0.75, max_value=+pi * 0.75),
st.floats(min_value=0.001, max_value=1000_000.0),
)
def test_bounding_window_center_of_dataset(
self, latitude: float, longitude: float, radius: float
) -> None:
"""Tests that the bounding box is correctly calculated if the query point is not at the border."""
# pylint: disable=too-many-locals
win_1, win_2 = self.dataset.get_bounding_windows_around(latitude, longitude, radius)
self.assertIsNone(win_2, "only one window shall be returned")
# Get the geographical extends in degrees, not radians:
left, bottom, right, top = self.dataset.dataset.window_bounds(win_1)
# Check the position of the window
latitude_deg, longitude_deg = degrees(latitude), degrees(longitude)
self.assertLessEqual(bottom, latitude_deg + 1e-12)
self.assertGreaterEqual(top, latitude_deg - 1e-12)
self.assertAlmostEqual(
(top + bottom) / 2,
latitude_deg,
delta=self.MAX_POINT_DISTANCE_DEG,
msg="window should be vertically centered around the given center point",
)
self.assertLessEqual(left, longitude_deg + 1e-12)
self.assertGreaterEqual(right, longitude_deg - 1e-12)
self.assertAlmostEqual(
(right + left) / 2,
longitude_deg,
delta=self.MAX_POINT_DISTANCE_DEG,
msg="window should be horizontally centered around the given center point",
)
# Check the size of the window
self.assertLessEqual(left, right)
self.assertLessEqual(bottom, top)
# Distances are uniform along longitudes
radius_deg = degrees(meters2rad(radius))
self.assertGreaterEqual(top - bottom, 2 * radius_deg - 1e-12)
self.assertLessEqual(top - bottom, 2 * radius_deg + 3 * self.MAX_POINT_DISTANCE_DEG)
# Distances are more complicated along the latitudes
left_side_center = PolarLocation(latitude=(top + bottom) / 2, longitude=left)
right_side_center = PolarLocation(latitude=(top + bottom) / 2, longitude=right)
# Use approximate=True for a spherical model
distance_horizontal = left_side_center.distance(right_side_center, approximate=True)
# The rough checking of the bounding boxes is very coarse and was determined by fiddling until it
# works
# This part of the test guarantees that the windows is roughly the right size, but pinning it down to
# exact number is hard since we round the discrete window bounds, map them to geographical coordinates
# and then have to deal with floating point inaccuracies
self.assertGreaterEqual(distance_horizontal, 2 * radius - 6 * self.MAX_POINT_DISTANCE)
self.assertLessEqual(distance_horizontal, 2 * radius + 4 * self.MAX_POINT_DISTANCE)
@given(
st.floats(min_value=-pi / 2 * 0.95, max_value=+pi / 2 * 0.95),
st.one_of(
[st.floats(min_value=-pi, max_value=-pi * 0.995), st.floats(min_value=+pi * 0.995, max_value=+pi)]
),
st.floats(min_value=200_000.0, max_value=1_000_000.0),
)
def test_bounding_window_left_and_right_side(
self, latitude: float, longitude: float, radius: float
) -> None:
"""Tests that the bounding box is correctly calculated if the query point is at the border.
Very high latitudes (near the poles) are not tested as there might be a single (albeit very wide)
window being returned. For the same reason, only moderate radii are tested.
"""
window_1, window_2 = self.dataset.get_bounding_windows_around(latitude, longitude, radius)
# We need a plain assert here for type checking
assert window_2 is not None, "two windows should be returned"
self.assertFalse(intersect(window_1, window_2), "windows may never overlap")
# Also test the same as in :meth:`~test_bounding_window_intersection_empty`
self.assertEqual(window_1.height, window_2.height)
self.assertGreaterEqual(window_1.height, 1)
self.assertGreaterEqual(window_1.width, 1)
self.assertGreaterEqual(window_2.width, 1)
self.assertLessEqual(window_1.height, self.dataset.dataset.height)
self.assertLessEqual(window_1.width + window_2.width, self.dataset.dataset.width)
@given(
st.floats(min_value=-pi / 2, max_value=+pi / 2),
st.floats(min_value=-pi, max_value=+pi),
st.floats(min_value=0.001, max_value=100_000_000.0),
)
def test_bounding_window_general_properties(
self, latitude: float, longitude: float, radius: float
) -> None:
"""Tests some more general properties that should hold for all windows and window pairs.
In particular, it makes sure that even if a window pair is very wide, the intersection is always
empty.
"""
window_1, window_2 = self.dataset.get_bounding_windows_around(latitude, longitude, radius)
# Make sure that everything in window_1 is rounded
self.assertIsInstance(window_1.col_off, int)
self.assertIsInstance(window_1.row_off, int)
self.assertIsInstance(window_1.height, int)
self.assertIsInstance(window_1.width, int)
# Test some general properties of window_1
self.assertTrue(radius == 0 or window_1.height >= 1)
self.assertLessEqual(window_1.height, self.dataset.dataset.height)
self.assertTrue(radius == 0 or window_1.width >= 1)
self.assertLessEqual(window_1.width, self.dataset.dataset.width)
if window_2 is not None:
self.assertGreater(radius, 0)
# Make sure that everything in window_2 is rounded
self.assertIsInstance(window_2.col_off, int)
self.assertIsInstance(window_2.row_off, int)
self.assertIsInstance(window_2.height, int)
self.assertIsInstance(window_2.width, int)
# Test some general properties of window_2 in relation to window_1
self.assertEqual(window_1.height, window_2.height)
self.assertGreaterEqual(window_2.width, 1)
self.assertLessEqual(window_1.width + window_2.width, self.dataset.dataset.width)
self.assertFalse(intersect(window_1, window_2), "windows may never overlap")
@given(
st.floats(min_value=-pi / 2 * 0.75, max_value=+pi / 2 * 0.75),
st.floats(min_value=-pi * 0.75, max_value=+pi * 0.75),
st.floats(min_value=0.001, max_value=1000_000.0),
)
def test_meshgrid_generation(self, latitude: float, longitude: float, radius: float) -> None:
"""Tests that msehgrids are generated correctly no matter whether radians are used or not.
Uses the data generation of :meth:`~test_bounding_window_center_of_dataset`.
"""
window, window_empty = self.dataset.get_bounding_windows_around(latitude, longitude, radius)
self.assertIsNone(window_empty, "only one window shall be returned")
lats_deg, lons_deg = self.dataset.lat_lon_meshgrid_for(window, window_empty, radians=False)
lats_rad, lons_rad = self.dataset.lat_lon_meshgrid_for(window, window_empty, radians=True)
assert_array_almost_equal(rad2deg(lats_rad), lats_deg)
assert_array_almost_equal(rad2deg(lons_rad), lons_deg)

View File

@ -0,0 +1,132 @@
"""
Tests the transformers in :mod:`pyrate.common.raster_datasets.transformer_base` and in
:mod:`pyrate.common.raster_datasets.transformers_concrete`.
"""
# Testing
from unittest import TestCase
# Scientific
from numpy import array
from numpy import empty
from numpy import float32
from numpy import int16
from numpy.testing import assert_array_equal
from numpy import uint16
from numpy import uint32
from pandas import DataFrame
from pandas import Series
from pandas.testing import assert_frame_equal
from pandas.testing import assert_series_equal
# Module under test
from pyrate.common.raster_datasets.transformers_concrete import BathymetricTransformer
from pyrate.common.raster_datasets.transformers_concrete import ConstantTransformer
# Graph generation
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
class TestGetNodePropertiesWithConstantTransformer(TestCase):
"""Ensure that the :meth:`pyrate.plan.graph.GeoNavigationGraph.append_properties` works correctly."""
def test_get_node_properties_empty_coordinates(self) -> None:
"""Tests getting properties for a graph without nodes."""
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes=empty((0,)), longitudes=empty((0,)), edges=empty((0, 2)), node_radius=111.111
)
transformers = [ConstantTransformer(42, uint32, "prop_1"), ConstantTransformer(43, uint16, "prop_2")]
graph.append_properties(transformers)
self.assertEqual(len(graph.node_properties), 0)
assert_array_equal(graph.node_properties.columns, ["prop_1", "prop_2"])
def test_get_node_properties_no_transformers(self) -> None:
"""Tests getting properties without a transformer."""
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes=array([0, 1]), longitudes=array([0, 0]), edges=array([[0, 1]]), node_radius=111.111
)
graph.append_properties([]) # empty!
self.assertEqual(len(graph.node_properties), 2)
assert_array_equal(graph.node_properties.columns, [])
def test_get_node_properties_single_transformer(self) -> None:
"""Tests getting properties using only a single transformer."""
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes=array([0, 1]),
longitudes=array([0, 0]),
edges=array([[0, 1]]),
node_radius=0.0, # some weird radius
)
# now we use `append_property` to append a single one
graph.append_property(ConstantTransformer(33, uint32, "prop_1"))
self.assertEqual(len(graph.node_properties), 2)
assert_frame_equal(graph.node_properties, DataFrame(data={"prop_1": [33, 33]}, dtype=uint32))
def test_get_node_properties_single_transformer_str_datatype(self) -> None:
"""Tests getting properties using only a single transformer and a string datatype."""
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes=array([0]),
longitudes=array([0]),
edges=array([[0, 0]]), # edge to itself
node_radius=111.111,
)
# now we use `append_property` to append a single one
data_type = "U10" # must give string data type explicitly and not with np.str or "U"
graph.append_property(ConstantTransformer("content", data_type, "prop_1"))
self.assertEqual(len(graph.node_properties), 1)
assert_frame_equal(graph.node_properties, DataFrame(data={"prop_1": ["content"]}, dtype=data_type))
def test_get_node_properties_multiple_transformers(self) -> None:
"""Tests getting properties using multiple transformers."""
graph = GeoNavigationGraph.from_coordinates_degrees(
latitudes=array([0, 1]), longitudes=array([0, 0]), edges=array([[0, 1]]), node_radius=111.111
)
# now we use `append_property` to append a single one
graph.append_properties(
[ConstantTransformer(33, uint32, "prop_1"), ConstantTransformer(99, int16, "prop_2")]
)
self.assertEqual(len(graph.node_properties), 2)
assert_array_equal(graph.node_properties.columns, ["prop_1", "prop_2"])
assert_series_equal(
graph.node_properties["prop_1"], Series(data=[33, 33], dtype=uint32, name="prop_1")
)
assert_series_equal(
graph.node_properties["prop_2"], Series(data=[99, 99], dtype=int16, name="prop_2")
)
class TestBathymetricTransformer(TestCase):
"""Tests :class:`pyrate.common.raster_datasets.transformers_concrete.BathymetricTransformer`."""
def test_all_modes(self) -> None:
"""Tests all modes at once."""
# create a coarse grid
distance_meters = 1000_000
graph = create_earth_graph(min_required_frequency(distance_meters, in_meters=True))
# fetch properties
modes = list(BathymetricTransformer.Modes)
graph.append_property(BathymetricTransformer(_open_test_geo_dataset(), modes))
properties = graph.node_properties
# check that the returned properties are all floats
self.assertTrue((properties.dtypes == float32).all())
def test_no_data(self) -> None:
"""Tests that querying for data where there are no data points in the result range raises an error."""
for mode in list(BathymetricTransformer.Modes):
with self.subTest(mode.name), self.assertRaises(ValueError):
with BathymetricTransformer(_open_test_geo_dataset(), [mode]) as transformer:
# This works by querying for a point (at 1e-3°N 1e-3°E), where there is no data point
# within 1e-9 meters in the underlying dataset
# This should trigger an exception (e.g. because the average depth over zero data
# points is not clearly)
transformer.get_transformed_at_nodes(
latitudes=array([1e-3]), longitudes=array([1e-3]), radius=1e-9
)