From e4aace4243da20d41728efb9e0a7db025f108002 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Thu, 16 Jul 2026 04:27:36 -0400 Subject: [PATCH 1/2] tests: Add pytest coverage for the temporal framework and R-tree bindings Add executed pytest tests for parts of the temporal framework and the R-tree ctypes bindings that no test in CI covered. The cases build their datasets in memory, so they need neither a temporal database nor a GRASS session. - grass_temporal_datetime_math_test.py: datetime increment by string and adjustment of a datetime to a granularity. - grass_temporal_granularity_test.py: absolute-time granularity of map lists of intervals, time points and a mix of both. - grass_temporal_topology_test.py: the temporal topology builder relations (overlaps, overlapped, follows, precedes, contains, during) and the map list sorting keys. Following TemporalExtent.overlaps, an interval that starts and ends earlier overlaps the later one. - grass_temporal_spatial_extent_test.py: the 2D and 3D spatial extent relations and intersection. - lib_vector_rtree_ctypes_test.py: the 1D to 4D R-tree search through the grass.lib ctypes bindings. These checks previously existed only in grass.temporal.unit_tests, which could not be imported and was not run, and in doctests that CI does not execute. Prepared with the assistance of AI (Claude Code). --- .../tests/lib_vector_rtree_ctypes_test.py | 70 +++++++ .../grass_temporal_datetime_math_test.py | 65 +++++++ .../tests/grass_temporal_granularity_test.py | 103 +++++++++++ .../grass_temporal_spatial_extent_test.py | 171 ++++++++++++++++++ .../tests/grass_temporal_topology_test.py | 165 +++++++++++++++++ 5 files changed, 574 insertions(+) create mode 100644 lib/vector/rtree/tests/lib_vector_rtree_ctypes_test.py create mode 100644 python/grass/temporal/tests/grass_temporal_datetime_math_test.py create mode 100644 python/grass/temporal/tests/grass_temporal_granularity_test.py create mode 100644 python/grass/temporal/tests/grass_temporal_spatial_extent_test.py create mode 100644 python/grass/temporal/tests/grass_temporal_topology_test.py diff --git a/lib/vector/rtree/tests/lib_vector_rtree_ctypes_test.py b/lib/vector/rtree/tests/lib_vector_rtree_ctypes_test.py new file mode 100644 index 00000000000..3d7c63bd58d --- /dev/null +++ b/lib/vector/rtree/tests/lib_vector_rtree_ctypes_test.py @@ -0,0 +1,70 @@ +"""Tests for the R-tree search through the ctypes bindings + +The R-tree supports one to four dimensions. Each case below inserts the ten +rectangles [i, i + 1] along every dimension and searches with [2, 7] along +every dimension, so the same ten rectangles and the same expected result apply +to all dimensions. Rectangles that only touch the search rectangle at a +boundary are part of the result, which is why [1, 2] and [7, 8] are included. + +The R-tree is a pure in-memory structure, so no GRASS session is needed here. +""" + +from ctypes import byref + +import pytest + +from grass.lib import gis, rtree, vector + +# The identifiers the search is expected to return. Identifiers are one-based, +# so the rectangle [i, i + 1] has the identifier i + 1. +EXPECTED_IDS = [2, 3, 4, 5, 6, 7, 8] + +# The function setting the bounds of a rectangle, one per dimensionality. +SET_RECT = { + 1: rtree.RTreeSetRect1D, + 2: rtree.RTreeSetRect2D, + 3: rtree.RTreeSetRect3D, + 4: rtree.RTreeSetRect4D, +} + + +def set_bounds(rect, tree, minimum, maximum) -> None: + """Set the same bounds along every dimension of the tree""" + dimensions = tree.contents.ndims + SET_RECT[dimensions](rect, tree, *([minimum, maximum] * dimensions)) + + +def search(tree, minimum, maximum): + """Return the identifiers of the rectangles overlapping the search bounds""" + rect = rtree.RTreeAllocRect(tree) + set_bounds(rect, tree, minimum, maximum) + + found = gis.ilist() + number = vector.RTreeSearch2(tree, rect, byref(found)) + rtree.RTreeFreeRect(rect) + + identifiers = sorted(found.value[i] for i in range(found.n_values)) + # The return value counts the rectangles the search put into the list. + assert number == len(identifiers) + return identifiers + + +@pytest.fixture(params=[1, 2, 3, 4], ids=["1d", "2d", "3d", "4d"]) +def tree(request): + """An R-tree of the given dimensionality holding the ten rectangles""" + tree = rtree.RTreeCreateTree(-1, 0, request.param) + for i in range(10): + rect = rtree.RTreeAllocRect(tree) + set_bounds(rect, tree, float(i), float(i + 1)) + rtree.RTreeInsertRect(rect, i + 1, tree) + yield tree + rtree.RTreeDestroyTree(tree) + + +def test_search_returns_the_overlapping_rectangles(tree) -> None: + assert search(tree, 2.0, 7.0) == EXPECTED_IDS + + +def test_search_without_a_match_returns_nothing(tree) -> None: + """A search rectangle beyond every inserted rectangle finds nothing""" + assert search(tree, 20.0, 30.0) == [] diff --git a/python/grass/temporal/tests/grass_temporal_datetime_math_test.py b/python/grass/temporal/tests/grass_temporal_datetime_math_test.py new file mode 100644 index 00000000000..92767f47b58 --- /dev/null +++ b/python/grass/temporal/tests/grass_temporal_datetime_math_test.py @@ -0,0 +1,65 @@ +"""Tests for datetime arithmetic and granularity adjustment + +Both functions are pure datetime arithmetic, so no GRASS session and no +temporal database are needed here. +""" + +from datetime import datetime + +import pytest + +from grass.temporal.datetime_math import ( + adjust_datetime_to_granularity, + increment_datetime_by_string, +) + + +@pytest.mark.parametrize( + ("start", "increment", "expected"), + [ + # A single increment string may combine every supported unit. Seconds + # carry into minutes and weeks are added on top of the days. + ( + datetime(2001, 9, 1), + "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years", + datetime(2003, 2, 18, 12, 5), + ), + # A month increment lands on the same day of the following month. + (datetime(2001, 11, 1), "1 months", datetime(2001, 12, 1)), + # More than twelve months has to roll the year over. + (datetime(2001, 11, 1), "13 months", datetime(2002, 12, 1)), + (datetime(2001, 1, 1), "72 months", datetime(2007, 1, 1)), + ], +) +def test_increment_datetime_by_string(start, increment, expected) -> None: + assert increment_datetime_by_string(start, increment) == expected + + +@pytest.mark.parametrize( + ("granularity", "expected"), + [ + # The date is truncated to the granularity unit, so a granularity + # finer than or equal to the seconds of the date leaves it unchanged. + ("5 seconds", datetime(2001, 8, 8, 12, 30, 30)), + ("20 minutes", datetime(2001, 8, 8, 12, 30)), + ("3 hours", datetime(2001, 8, 8, 12, 0)), + ("5 days", datetime(2001, 8, 8, 0, 0)), + # Weeks snap back to the Monday of the week, 2001-08-08 is a Wednesday. + ("2 weeks", datetime(2001, 8, 6, 0, 0)), + ("6 months", datetime(2001, 8, 1, 0, 0)), + ("2 years", datetime(2001, 1, 1, 0, 0)), + # With several units the finest one present wins, because truncating to + # it already satisfies all the coarser ones. + ( + "2 years, 3 months, 5 days, 3 hours, 3 minutes, 2 seconds", + datetime(2001, 8, 8, 12, 30, 30), + ), + ("3 months, 5 days, 3 minutes", datetime(2001, 8, 8, 12, 30)), + ("3 weeks, 5 days", datetime(2001, 8, 8, 0, 0)), + ], +) +def test_adjust_datetime_to_granularity(granularity, expected) -> None: + assert ( + adjust_datetime_to_granularity(datetime(2001, 8, 8, 12, 30, 30), granularity) + == expected + ) diff --git a/python/grass/temporal/tests/grass_temporal_granularity_test.py b/python/grass/temporal/tests/grass_temporal_granularity_test.py new file mode 100644 index 00000000000..f545a85df1a --- /dev/null +++ b/python/grass/temporal/tests/grass_temporal_granularity_test.py @@ -0,0 +1,103 @@ +"""Tests for the absolute time granularity computation + +The granularity of a map list is the largest unit in which the start and end +times of every map can be expressed. Maps are built in memory only, so no +GRASS session and no temporal database are needed here. +""" + +from datetime import datetime + +import pytest + +from grass.temporal.datetime_math import increment_datetime_by_string +from grass.temporal.space_time_datasets import RasterDataset +from grass.temporal.temporal_granularity import compute_absolute_time_granularity + + +def interval_maps(start, increment, count): + """Return count maps covering consecutive intervals of the given increment""" + maps = [] + for i in range(count): + map_ = RasterDataset(None) + map_.set_absolute_time( + increment_datetime_by_string(start, increment, i), + increment_datetime_by_string(start, increment, i + 1), + ) + maps.append(map_) + return maps + + +def point_maps(start, increment, count): + """Return count maps which are time points spaced by the given increment""" + maps = [] + for i in range(count): + map_ = RasterDataset(None) + map_.set_absolute_time(increment_datetime_by_string(start, increment, i), None) + maps.append(map_) + return maps + + +@pytest.mark.parametrize( + ("start", "increment", "count", "expected"), + [ + (datetime(2001, 1, 1), "1 year", 10, "1 year"), + (datetime(2001, 1, 1), "3 years", 10, "3 years"), + (datetime(2001, 5, 1), "1 month", 20, "1 month"), + (datetime(2001, 1, 1), "3 months", 20, "3 months"), + (datetime(2001, 1, 1), "1 day", 6, "1 day"), + (datetime(2001, 1, 14), "14 days", 6, "14 days"), + (datetime(2001, 6, 12), "6 hours", 20, "6 hours"), + (datetime(2001, 1, 1), "20 minutes", 20, "20 minutes"), + (datetime(2001, 12, 31, 12, 30, 30), "3600 seconds", 24, "3600 seconds"), + # Mixed units collapse to the finest unit that can express every + # boundary. Months and days cannot be combined, so days remain. + (datetime(2001, 3, 1), "1 month, 4 days", 20, "1 day"), + (datetime(2001, 2, 11), "1 days, 1 hours", 20, "25 hours"), + (datetime(2001, 1, 1), "5 hours, 25 minutes", 20, "325 minutes"), + (datetime(2001, 1, 1), "5 minutes, 30 seconds", 20, "330 seconds"), + (datetime(2001, 12, 31), "60 minutes, 30 seconds", 24, "3630 seconds"), + ], +) +def test_granularity_of_intervals(start, increment, count, expected) -> None: + assert ( + compute_absolute_time_granularity(interval_maps(start, increment, count)) + == expected + ) + + +@pytest.mark.parametrize( + ("start", "increment", "count", "expected"), + [ + (datetime(2001, 12, 31, 12, 30, 30), "3600 seconds", 24, "3600 seconds"), + (datetime(2001, 12, 31), "20 days", 24, "20 days"), + (datetime(2001, 12, 1), "5 months", 24, "5 months"), + ], +) +def test_granularity_of_time_points(start, increment, count, expected) -> None: + assert ( + compute_absolute_time_granularity(point_maps(start, increment, count)) + == expected + ) + + +@pytest.mark.parametrize( + ("interval_start", "point_start", "increment", "count", "expected"), + [ + ( + datetime(2001, 12, 31, 12, 30, 30), + datetime(2002, 2, 1, 12, 30, 30), + "3600 seconds", + 24, + "3600 seconds", + ), + (datetime(2001, 1, 1), datetime(2001, 2, 2), "2 days", 8, "2 days"), + ], +) +def test_granularity_of_intervals_and_time_points( + interval_start, point_start, increment, count, expected +) -> None: + """A list may mix intervals and time points and still have a granularity""" + maps = interval_maps(interval_start, increment, count) + point_maps( + point_start, increment, count + ) + assert compute_absolute_time_granularity(maps) == expected diff --git a/python/grass/temporal/tests/grass_temporal_spatial_extent_test.py b/python/grass/temporal/tests/grass_temporal_spatial_extent_test.py new file mode 100644 index 00000000000..cbc859c278d --- /dev/null +++ b/python/grass/temporal/tests/grass_temporal_spatial_extent_test.py @@ -0,0 +1,171 @@ +"""Tests for the 2D and 3D spatial extent relations + +Extents are plain value objects, so no GRASS session and no temporal database +are needed here. +""" + +import pytest + +from grass.temporal.spatial_extent import SpatialExtent + +# The extent every case below is compared against. +REFERENCE = {"north": 80, "south": 20, "east": 60, "west": 10, "bottom": -50, "top": 50} + + +def extent(**kwargs): + """Return the reference extent with the given bounds overridden + + Called with no arguments it returns the reference extent itself; each + keyword argument (north, south, east, west, bottom, top) replaces that + one bound. + """ + return SpatialExtent(**{**REFERENCE, **kwargs}) + + +@pytest.mark.parametrize( + "other", + [ + # The intersection of the reference extent with an extent it fully + # covers is that extent itself, in 2D and in 3D. + {}, + {"north": 40, "south": 30}, + {"north": 40, "south": 30, "west": 30}, + {"north": 40, "south": 30, "west": 30, "bottom": -30}, + {"north": 40, "south": 30, "west": 30, "bottom": -30, "top": 30}, + ], +) +def test_intersect_with_covered_extent_returns_that_extent(other) -> None: + covered = extent(**other) + result = extent().intersect(covered) + + assert result.get_north() == covered.get_north() + assert result.get_south() == covered.get_south() + assert result.get_west() == covered.get_west() + assert result.get_east() == covered.get_east() + assert result.get_bottom() == covered.get_bottom() + assert result.get_top() == covered.get_top() + + +@pytest.mark.parametrize( + ("other", "expected_2d", "expected_3d"), + [ + # Identical extents are equivalent in both 2D and 3D. + ({}, "equivalent", "equivalent"), + # Sharing a boundary while being smaller in one direction is "cover". + ({"north": 70}, "cover", "cover"), + ({"north": 70, "south": 30}, "cover", "cover"), + ({"north": 70, "south": 30, "east": 50}, "cover", "cover"), + # Strictly inside in 2D, but the top and bottom still touch, so the 3D + # relation stays "cover" rather than becoming "contain". + ({"north": 70, "south": 30, "east": 50, "west": 20}, "contain", "cover"), + # Strictly inside in every direction is "contain". + ( + { + "north": 70, + "south": 30, + "east": 50, + "west": 20, + "bottom": -40, + "top": 40, + }, + "contain", + "contain", + ), + ], +) +def test_spatial_relation_of_smaller_extent(other, expected_2d, expected_3d) -> None: + assert extent().spatial_relation_2d(extent(**other)) == expected_2d + assert extent().spatial_relation(extent(**other)) == expected_3d + + +@pytest.mark.parametrize( + ("other", "expected"), + [ + # The inverse of "cover" is "covered" and the inverse of "contain" is "in". + ({"north": 70, "south": 30}, "covered"), + ( + { + "north": 70, + "south": 30, + "east": 50, + "west": 20, + "bottom": -40, + "top": 40, + }, + "in", + ), + ], +) +def test_spatial_relation_is_inverted_when_arguments_swap(other, expected) -> None: + assert extent(**other).spatial_relation(extent()) == expected + + +@pytest.mark.parametrize( + ("other", "expected_2d", "expected_3d"), + [ + # Reaching past the reference extent in one direction only is an overlap. + ( + { + "north": 90, + "south": 30, + "east": 50, + "west": 20, + "bottom": -40, + "top": 40, + }, + "overlap", + "overlap", + ), + # The reference extent lies inside the other one in 2D, but sticks out + # in the third dimension, so in 3D the two only overlap. + ( + {"north": 90, "south": 5, "east": 70, "west": 5, "bottom": -40, "top": 40}, + "in", + "overlap", + ), + ], +) +def test_spatial_relation_of_overlapping_extent( + other, expected_2d, expected_3d +) -> None: + assert extent().spatial_relation_2d(extent(**other)) == expected_2d + assert extent().spatial_relation(extent(**other)) == expected_3d + + +@pytest.mark.parametrize( + ("one", "other", "expected"), + [ + # Extents that only touch along a face "meet", never overlap. + ( + {"north": 80, "south": 60}, + {"north": 60, "south": 20}, + "meet", + ), + ( + {"north": 60, "south": 40}, + {"north": 80, "south": 60}, + "meet", + ), + ( + {"north": 80, "south": 40, "east": 60, "west": 40}, + {"north": 80, "south": 40, "east": 40, "west": 20}, + "meet", + ), + # Touching only along the top and bottom faces also "meets". + ( + {"east": 60, "west": 20, "north": 80, "south": 40, "bottom": 0, "top": 50}, + {"east": 60, "west": 20, "north": 80, "south": 40, "bottom": -50, "top": 0}, + "meet", + ), + ], +) +def test_spatial_relation_of_touching_extents(one, other, expected) -> None: + assert extent(**one).spatial_relation(extent(**other)) == expected + + +def test_spatial_relation_of_separate_extents_is_disjoint() -> None: + one = extent(north=80, south=40, east=40, west=20) + other = extent(north=40, south=20, east=60, west=40) + + assert one.spatial_relation_2d(other) == "disjoint" + assert one.spatial_relation(other) == "disjoint" diff --git a/python/grass/temporal/tests/grass_temporal_topology_test.py b/python/grass/temporal/tests/grass_temporal_topology_test.py new file mode 100644 index 00000000000..82ce00d91c6 --- /dev/null +++ b/python/grass/temporal/tests/grass_temporal_topology_test.py @@ -0,0 +1,165 @@ +"""Tests for the temporal topology builder and the map list sorting keys + +The relations follow Allen and Ferguson 1994, as cited by +grass.temporal.temporal_extent: an interval starting earlier and ending inside +a later one "overlaps" it, and the later one is "overlapped" by it. + +Maps are built in memory only, so no GRASS session and no temporal database +are needed here. +""" + +from datetime import datetime + +import pytest + +from grass.temporal.abstract_dataset import ( + AbstractDatasetComparisonKeyEndTime, + AbstractDatasetComparisonKeyStartTime, +) +from grass.temporal.space_time_datasets import RasterDataset +from grass.temporal.spatio_temporal_relationships import SpatioTemporalTopologyBuilder + + +def maps_from(intervals): + """Return maps for a list of (identifier, start, end) tuples""" + maps = [] + for ident, start, end in intervals: + map_ = RasterDataset(ident=ident) + map_.set_absolute_time(start, end) + maps.append(map_) + return maps + + +@pytest.fixture +def consecutive_maps(): + """Five maps covering consecutive months without gaps""" + return maps_from( + [ + ("d_%i@a" % (i + 1), datetime(2001, i + 1, 1), datetime(2001, i + 2, 1)) + for i in range(5) + ] + ) + + +@pytest.fixture +def overlapping_maps(): + """Five maps with overlapping, contained and disjoint intervals""" + return maps_from( + [ + ("d_1@b", datetime(2001, 1, 14), datetime(2001, 3, 14)), + ("d_2@b", datetime(2001, 2, 1), datetime(2001, 4, 1)), + ("d_3@b", datetime(2001, 2, 14), datetime(2001, 4, 30)), + ("d_4@b", datetime(2001, 4, 2), datetime(2001, 4, 30)), + ("d_5@b", datetime(2001, 5, 1), datetime(2001, 5, 14)), + ] + ) + + +def related_ids(maps, relation): + """Return the identifiers related to each map by the given relation""" + return [ + [related.get_id() for related in (getattr(map_, "get_" + relation)() or [])] + for map_ in maps + ] + + +def test_builder_iterates_in_input_order(consecutive_maps) -> None: + builder = SpatioTemporalTopologyBuilder() + builder.build(consecutive_maps) + + assert [map_.get_id() for map_ in builder] == [ + map_.get_id() for map_ in consecutive_maps + ] + + +def test_consecutive_maps_only_follow_and_precede(consecutive_maps) -> None: + """Intervals that touch but do not overlap only follow and precede""" + builder = SpatioTemporalTopologyBuilder() + builder.build(consecutive_maps) + + assert related_ids(consecutive_maps, "precedes") == [ + ["d_2@a"], + ["d_3@a"], + ["d_4@a"], + ["d_5@a"], + [], + ] + assert related_ids(consecutive_maps, "follows") == [ + [], + ["d_1@a"], + ["d_2@a"], + ["d_3@a"], + ["d_4@a"], + ] + assert related_ids(consecutive_maps, "overlaps") == [[]] * 5 + + +def test_overlaps_points_from_the_earlier_to_the_later_interval( + overlapping_maps, +) -> None: + """d_1@b starts before d_2@b and d_3@b and ends inside them, so it overlaps them""" + builder = SpatioTemporalTopologyBuilder() + builder.build(overlapping_maps) + + assert related_ids(overlapping_maps, "overlaps") == [ + ["d_2@b", "d_3@b"], + ["d_3@b"], + [], + [], + [], + ] + assert related_ids(overlapping_maps, "overlapped") == [ + [], + ["d_1@b"], + ["d_1@b", "d_2@b"], + [], + [], + ] + + +def test_contains_and_during_are_inverse(overlapping_maps) -> None: + """d_4@b lies inside d_3@b, so d_3@b contains it and it is during d_3@b""" + builder = SpatioTemporalTopologyBuilder() + builder.build(overlapping_maps) + + assert related_ids(overlapping_maps, "contains") == [[], [], ["d_4@b"], [], []] + assert related_ids(overlapping_maps, "during") == [[], [], [], ["d_3@b"], []] + + +def test_topology_between_two_map_lists(consecutive_maps, overlapping_maps) -> None: + """Building with two lists relates the maps of one list to the other""" + builder = SpatioTemporalTopologyBuilder() + builder.build(consecutive_maps, overlapping_maps) + + # d_4@a covers April, so it starts inside d_3@b and ends after it. + assert related_ids(consecutive_maps, "overlapped")[3] == ["d_3@b"] + assert related_ids(consecutive_maps, "follows")[3] == ["d_2@b"] + assert related_ids(consecutive_maps, "precedes")[3] == ["d_5@b"] + assert related_ids(consecutive_maps, "contains")[3] == ["d_4@b"] + # d_3@a covers March, which lies inside both d_2@b and d_3@b. + assert related_ids(consecutive_maps, "during")[2] == ["d_2@b", "d_3@b"] + + +@pytest.fixture +def unsorted_maps(): + """Three maps whose order in the list is not their temporal order""" + return maps_from( + [ + ("d_1@a", datetime(2001, 2, 1), datetime(2001, 3, 1)), + ("d_2@a", datetime(2001, 1, 1), datetime(2001, 2, 1)), + ("d_3@a", datetime(2001, 3, 1), datetime(2001, 4, 1)), + ] + ) + + +@pytest.mark.parametrize( + "key", + [AbstractDatasetComparisonKeyStartTime, AbstractDatasetComparisonKeyEndTime], +) +def test_map_list_sorting(unsorted_maps, key) -> None: + """The maps do not overlap, so start and end time sort them the same way""" + assert [map_.get_id() for map_ in sorted(unsorted_maps, key=key)] == [ + "d_2@a", + "d_1@a", + "d_3@a", + ] From b353096bf946433235e5f60a07c503490bcdb4d8 Mon Sep 17 00:00:00 2001 From: Vaclav Petras Date: Thu, 16 Jul 2026 04:27:36 -0400 Subject: [PATCH 2/2] grass.temporal: Remove the dead unit_tests module The module has been unimportable since the star imports were removed in 0c83c976b2: it imports adjust_datetime_to_granularity from temporal_granularity, but that function lives in datetime_math. Nothing imports the module, no test runner collects it (it is neither a pytest tests/ file nor a gunittest testsuite/ file), and its own docstring calls it deprecated, so the breakage went unnoticed for years. Sphinx autodoc does reach it and reports the failed import as a warning. Repairing it would mean more than fixing the import. Two assertions in test_temporal_topology_builder expect the inverse of the current overlaps/overlapped semantics and fail once the module can be imported again, and the four rtree functions assert nothing at all, they only print. The checks that remain are already covered by doctests in datetime_math, temporal_granularity, spatial_extent and spatio_temporal_relationships; the adjust_datetime_to_granularity doctests are the same eleven cases the module used. Remove the module instead of resurrecting a test nothing runs. Drop it from the Makefile module list so it is no longer installed, and drop the pytest ignore that only existed to step around it. The investigation and the change were done with the help of AI (Claude Code). --- .github/workflows/pytest_args_gunittest.txt | 1 - python/grass/temporal/Makefile | 2 +- python/grass/temporal/unit_tests.py | 1336 ------------------- 3 files changed, 1 insertion(+), 1338 deletions(-) delete mode 100644 python/grass/temporal/unit_tests.py diff --git a/.github/workflows/pytest_args_gunittest.txt b/.github/workflows/pytest_args_gunittest.txt index 0f2e55be094..77e2edfb293 100644 --- a/.github/workflows/pytest_args_gunittest.txt +++ b/.github/workflows/pytest_args_gunittest.txt @@ -1,6 +1,5 @@ --ignore-glob=**/tests/** --ignore=gui/ ---ignore=python/grass/temporal/unit_tests.py lib/gis/testsuite/test_gis_lib_getl.py lib/init/testsuite/test_grass_tmp_mapset.py lib/vector/Vlib/testsuite/test_vlib_box.py diff --git a/python/grass/temporal/Makefile b/python/grass/temporal/Makefile index e85507061f7..e827161c3c9 100644 --- a/python/grass/temporal/Makefile +++ b/python/grass/temporal/Makefile @@ -8,7 +8,7 @@ GDIR = $(PYDIR)/grass DSTDIR = $(GDIR)/temporal DSTDIRPLY = $(DSTDIR)/ply -MODULES = ply/__init__ ply/lex ply/yacc base core abstract_dataset abstract_map_dataset abstract_space_time_dataset space_time_datasets open_stds factory gui_support list_stds register sampling metadata spatial_extent temporal_extent datetime_math temporal_granularity spatio_temporal_relationships unit_tests aggregation stds_export stds_import extract mapcalc univar_statistics temporal_topology_dataset_connector spatial_topology_dataset_connector c_libraries_interface temporal_algebra temporal_vector_algebra temporal_raster_base_algebra temporal_raster_algebra temporal_raster3d_algebra temporal_operator +MODULES = ply/__init__ ply/lex ply/yacc base core abstract_dataset abstract_map_dataset abstract_space_time_dataset space_time_datasets open_stds factory gui_support list_stds register sampling metadata spatial_extent temporal_extent datetime_math temporal_granularity spatio_temporal_relationships aggregation stds_export stds_import extract mapcalc univar_statistics temporal_topology_dataset_connector spatial_topology_dataset_connector c_libraries_interface temporal_algebra temporal_vector_algebra temporal_raster_base_algebra temporal_raster_algebra temporal_raster3d_algebra temporal_operator CLEAN_SUBDIRS = ply diff --git a/python/grass/temporal/unit_tests.py b/python/grass/temporal/unit_tests.py deleted file mode 100644 index 63ff4a0778d..00000000000 --- a/python/grass/temporal/unit_tests.py +++ /dev/null @@ -1,1336 +0,0 @@ -""" -Deprecated unittests - -(C) 2008-2011 by the GRASS Development Team -This program is free software under the GNU General Public -License (>=v2). Read the file COPYING that comes with GRASS -for details. - -:authors: Soeren Gebbert -""" - -import copy -from ctypes import byref -from datetime import datetime - -from grass.lib import gis, rtree, vector -from grass.script import core - -from .abstract_dataset import ( - AbstractDatasetComparisonKeyEndTime, - AbstractDatasetComparisonKeyStartTime, -) -from .core import init -from .datetime_math import increment_datetime_by_string -from .space_time_datasets import RasterDataset -from .spatial_extent import SpatialExtent -from .spatio_temporal_relationships import SpatioTemporalTopologyBuilder -from .temporal_granularity import ( - adjust_datetime_to_granularity, - compute_absolute_time_granularity, -) - -# Uncomment this to detect the error -core.set_raise_on_error(True) - -############################################################################### - - -def test_increment_datetime_by_string() -> None: - # First test - print("# Test 1") - dt = datetime(2001, 9, 1, 0, 0, 0) - string = "60 seconds, 4 minutes, 12 hours, 10 days, 1 weeks, 5 months, 1 years" - - dt1 = datetime(2003, 2, 18, 12, 5, 0) - dt2 = increment_datetime_by_string(dt, string) - - print(dt) - print(dt2) - - delta = dt1 - dt2 - - if delta.days != 0 or delta.seconds != 0: - core.fatal("increment computation is wrong %s" % (delta)) - - # Second test - print("# Test 2") - dt = datetime(2001, 11, 1, 0, 0, 0) - string = "1 months" - - dt1 = datetime(2001, 12, 1) - dt2 = increment_datetime_by_string(dt, string) - - print(dt) - print(dt2) - - delta = dt1 - dt2 - - if delta.days != 0 or delta.seconds != 0: - core.fatal("increment computation is wrong %s" % (delta)) - - # Third test - print("# Test 3") - dt = datetime(2001, 11, 1, 0, 0, 0) - string = "13 months" - - dt1 = datetime(2002, 12, 1) - dt2 = increment_datetime_by_string(dt, string) - - print(dt) - print(dt2) - - delta = dt1 - dt2 - - if delta.days != 0 or delta.seconds != 0: - core.fatal("increment computation is wrong %s" % (delta)) - - # 4. test - print("# Test 4") - dt = datetime(2001, 1, 1, 0, 0, 0) - string = "72 months" - - dt1 = datetime(2007, 1, 1) - dt2 = increment_datetime_by_string(dt, string) - - print(dt) - print(dt2) - - delta = dt1 - dt2 - - if delta.days != 0 or delta.seconds != 0: - core.fatal("increment computation is wrong %s" % (delta)) - - -############################################################################### - - -def test_adjust_datetime_to_granularity() -> None: - # First test - print("Test 1") - dt = datetime(2001, 8, 8, 12, 30, 30) - result = adjust_datetime_to_granularity(dt, "5 seconds") - correct = datetime(2001, 8, 8, 12, 30, 30) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # Second test - print("Test 2") - result = adjust_datetime_to_granularity(dt, "20 minutes") - correct = datetime(2001, 8, 8, 12, 30, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # Third test - print("Test 2") - result = adjust_datetime_to_granularity(dt, "20 minutes") - correct = datetime(2001, 8, 8, 12, 30, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 4. test - print("Test 4") - result = adjust_datetime_to_granularity(dt, "3 hours") - correct = datetime(2001, 8, 8, 12, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 5. test - print("Test 5") - result = adjust_datetime_to_granularity(dt, "5 days") - correct = datetime(2001, 8, 8, 0, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 6. test - print("Test 6") - result = adjust_datetime_to_granularity(dt, "2 weeks") - correct = datetime(2001, 8, 6, 0, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 7. test - print("Test 7") - result = adjust_datetime_to_granularity(dt, "6 months") - correct = datetime(2001, 8, 1, 0, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 8. test - print("Test 8") - result = adjust_datetime_to_granularity(dt, "2 years") - correct = datetime(2001, 1, 1, 0, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 9. test - print("Test 9") - result = adjust_datetime_to_granularity( - dt, "2 years, 3 months, 5 days, 3 hours, 3 minutes, 2 seconds" - ) - correct = datetime(2001, 8, 8, 12, 30, 30) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 10. test - print("Test 10") - result = adjust_datetime_to_granularity(dt, "3 months, 5 days, 3 minutes") - correct = datetime(2001, 8, 8, 12, 30, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - # 11. test - print("Test 11") - result = adjust_datetime_to_granularity(dt, "3 weeks, 5 days") - correct = datetime(2001, 8, 8, 0, 0, 0) - - delta = correct - result - - if delta.days != 0 or delta.seconds != 0: - core.fatal("Granularity adjustment computation is wrong %s" % (delta)) - - -############################################################################### - - -def test_compute_absolute_time_granularity() -> None: - # First we test intervals - print("Test 1") - maps = [] - a = datetime(2001, 1, 1) - increment = "1 year" - for i in range(10): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 2") - maps = [] - a = datetime(2001, 1, 1) - increment = "3 years" - for i in range(10): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 3") - maps = [] - a = datetime(2001, 5, 1) - increment = "1 month" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 4") - maps = [] - a = datetime(2001, 1, 1) - increment = "3 months" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 3") - maps = [] - a = datetime(2001, 1, 1) - increment = "1 day" - for i in range(6): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 4") - maps = [] - a = datetime(2001, 1, 14) - increment = "14 days" - for i in range(6): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 5") - maps = [] - a = datetime(2001, 3, 1) - increment = "1 month, 4 days" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - increment = "1 day" - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 6") - maps = [] - a = datetime(2001, 2, 11) - increment = "1 days, 1 hours" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - increment = "25 hours" - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 7") - maps = [] - a = datetime(2001, 6, 12) - increment = "6 hours" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 8") - maps = [] - a = datetime(2001, 1, 1) - increment = "20 minutes" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 9") - maps = [] - a = datetime(2001, 1, 1) - increment = "5 hours, 25 minutes" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - increment = "325 minutes" - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 10") - maps = [] - a = datetime(2001, 1, 1) - increment = "5 minutes, 30 seconds" - for i in range(20): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - increment = "330 seconds" - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 11") - maps = [] - a = datetime(2001, 12, 31) - increment = "60 minutes, 30 seconds" - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - increment = "3630 seconds" - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 12") - maps = [] - a = datetime(2001, 12, 31, 12, 30, 30) - increment = "3600 seconds" - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - print(start) - print(end) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - # Test absolute time points - - print("Test 13") - maps = [] - a = datetime(2001, 12, 31, 12, 30, 30) - increment = "3600 seconds" - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = None - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 14") - maps = [] - a = datetime(2001, 12, 31, 0, 0, 0) - increment = "20 days" - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = None - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 15") - maps = [] - a = datetime(2001, 12, 1, 0, 0, 0) - increment = "5 months" - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = None - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - # Test absolute time interval and points - - print("Test 16") - maps = [] - a = datetime(2001, 12, 31, 12, 30, 30) - increment = "3600 seconds" - - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - a = datetime(2002, 2, 1, 12, 30, 30) - for i in range(24): - start = increment_datetime_by_string(a, increment, i) - end = None - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - print("Test 17") - maps = [] - a = datetime(2001, 1, 1) - increment = "2 days" - - for i in range(8): - start = increment_datetime_by_string(a, increment, i) - end = increment_datetime_by_string(a, increment, i + 1) - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - a = datetime(2001, 2, 2) - for i in range(8): - start = increment_datetime_by_string(a, increment, i) - end = None - map = RasterDataset(None) - map.set_absolute_time(start, end) - maps.append(map) - - gran = compute_absolute_time_granularity(maps) - if increment != gran: - core.fatal("Wrong granularity reference %s != gran %s" % (increment, gran)) - - -############################################################################### - - -def test_spatial_extent_intersection() -> None: - # Generate the extents - - A = SpatialExtent(north=80, south=20, east=60, west=10, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=80, south=20, east=60, west=10, bottom=-50, top=50) - B.print_info() - C = A.intersect(B) - C.print_info() - - if ( - C.get_north() != B.get_north() - or C.get_south() != B.get_south() - or C.get_west() != B.get_west() - or C.get_east() != B.get_east() - or C.get_bottom() != B.get_bottom() - or C.get_top() != B.get_top() - ): - core.fatal("Wrong intersection computation") - - B = SpatialExtent(north=40, south=30, east=60, west=10, bottom=-50, top=50) - B.print_info() - C = A.intersect(B) - C.print_info() - - if ( - C.get_north() != B.get_north() - or C.get_south() != B.get_south() - or C.get_west() != B.get_west() - or C.get_east() != B.get_east() - or C.get_bottom() != B.get_bottom() - or C.get_top() != B.get_top() - ): - core.fatal("Wrong intersection computation") - - B = SpatialExtent(north=40, south=30, east=60, west=30, bottom=-50, top=50) - B.print_info() - C = A.intersect(B) - C.print_info() - - if ( - C.get_north() != B.get_north() - or C.get_south() != B.get_south() - or C.get_west() != B.get_west() - or C.get_east() != B.get_east() - or C.get_bottom() != B.get_bottom() - or C.get_top() != B.get_top() - ): - core.fatal("Wrong intersection computation") - - B = SpatialExtent(north=40, south=30, east=60, west=30, bottom=-30, top=50) - B.print_info() - C = A.intersect(B) - C.print_info() - - if ( - C.get_north() != B.get_north() - or C.get_south() != B.get_south() - or C.get_west() != B.get_west() - or C.get_east() != B.get_east() - or C.get_bottom() != B.get_bottom() - or C.get_top() != B.get_top() - ): - core.fatal("Wrong intersection computation") - - B = SpatialExtent(north=40, south=30, east=60, west=30, bottom=-30, top=30) - B.print_info() - C = A.intersect(B) - C.print_info() - - if ( - C.get_north() != B.get_north() - or C.get_south() != B.get_south() - or C.get_west() != B.get_west() - or C.get_east() != B.get_east() - or C.get_bottom() != B.get_bottom() - or C.get_top() != B.get_top() - ): - core.fatal("Wrong intersection computation") - - -############################################################################### - - -def test_spatial_relations() -> None: - # Generate the extents - - A = SpatialExtent(north=80, south=20, east=60, west=10, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=80, south=20, east=60, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "equivalent": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=20, east=60, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=60, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = B.spatial_relation_2d(A) - print(relation) - if relation != "covered": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = B.spatial_relation(A) - print(relation) - if relation != "covered": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=50, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = B.spatial_relation_2d(A) - print(relation) - if relation != "covered": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=50, west=20, bottom=-50, top=50) - - relation = B.spatial_relation(A) - print(relation) - if relation != "covered": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=50, west=20, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "contain": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=50, west=20, bottom=-40, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "cover": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=70, south=30, east=50, west=20, bottom=-40, top=40) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "contain": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = B.spatial_relation(A) - print(relation) - if relation != "in": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=90, south=30, east=50, west=20, bottom=-40, top=40) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "overlap": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "overlap": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=90, south=5, east=70, west=5, bottom=-40, top=40) - A.print_info() - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "in": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "overlap": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=90, south=5, east=70, west=5, bottom=-40, top=60) - A.print_info() - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "overlap": - core.fatal("Wrong spatial relation: %s" % (relation)) - - B = SpatialExtent(north=90, south=5, east=70, west=5, bottom=-60, top=60) - A.print_info() - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "in": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=60, east=60, west=10, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=60, south=20, east=60, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=60, south=40, east=60, west=10, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=80, south=60, east=60, west=10, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=40, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=90, south=30, east=60, west=40, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=70, south=50, east=60, west=40, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=60, south=20, east=60, west=40, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=40, south=20, east=60, west=40, bottom=-50, top=50) - B.print_info() - - relation = A.spatial_relation_2d(B) - print(relation) - if relation != "disjoint": - core.fatal("Wrong spatial relation: %s" % (relation)) - - relation = A.spatial_relation(B) - print(relation) - if relation != "disjoint": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=60, south=20, east=60, west=40, bottom=-60, top=60) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=40, west=20, bottom=-50, top=50) - A.print_info() - B = SpatialExtent(north=90, south=30, east=60, west=40, bottom=-40, top=40) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - A.print_info() - B = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - A.print_info() - B = SpatialExtent(north=80, south=50, east=60, west=30, bottom=-50, top=0) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - A.print_info() - B = SpatialExtent(north=70, south=50, east=50, west=30, bottom=-50, top=0) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - A.print_info() - B = SpatialExtent(north=90, south=30, east=70, west=10, bottom=-50, top=0) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - A.print_info() - B = SpatialExtent(north=70, south=30, east=50, west=10, bottom=-50, top=0) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - A.print_info() - B = SpatialExtent(north=80, south=40, east=60, west=20, bottom=0, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - A.print_info() - B = SpatialExtent(north=80, south=50, east=60, west=30, bottom=0, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - A.print_info() - B = SpatialExtent(north=70, south=50, east=50, west=30, bottom=0, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - A.print_info() - B = SpatialExtent(north=90, south=30, east=70, west=10, bottom=0, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - A = SpatialExtent(north=80, south=40, east=60, west=20, bottom=-50, top=0) - A.print_info() - B = SpatialExtent(north=70, south=30, east=50, west=10, bottom=0, top=50) - B.print_info() - - relation = A.spatial_relation(B) - print(relation) - if relation != "meet": - core.fatal("Wrong spatial relation: %s" % (relation)) - - -############################################################################### - - -def test_temporal_topology_builder() -> None: - map_listA = [] - - map_ = RasterDataset(ident="1@a") - map_.set_absolute_time(datetime(2001, 1, 1), datetime(2001, 2, 1)) - map_listA.append(copy.copy(map_)) - map_ = RasterDataset(ident="2@a") - map_.set_absolute_time(datetime(2001, 2, 1), datetime(2001, 3, 1)) - map_listA.append(copy.copy(map_)) - map_ = RasterDataset(ident="3@a") - map_.set_absolute_time(datetime(2001, 3, 1), datetime(2001, 4, 1)) - map_listA.append(copy.copy(map_)) - map_ = RasterDataset(ident="4@a") - map_.set_absolute_time(datetime(2001, 4, 1), datetime(2001, 5, 1)) - map_listA.append(copy.copy(map_)) - map_ = RasterDataset(ident="5@a") - map_.set_absolute_time(datetime(2001, 5, 1), datetime(2001, 6, 1)) - map_listA.append(copy.copy(map_)) - - tb = SpatioTemporalTopologyBuilder() - tb.build(map_listA) - - count = 0 - for map_ in tb: - print("[%s]" % (map_.get_name())) - map_.print_topology_info() - if map_.get_id() != map_listA[count].get_id(): - core.fatal( - "Error building temporal topology <%s> != <%s>" - % (map_.get_id(), map_listA[count].get_id()) - ) - count += 1 - - map_listB = [] - - map_ = RasterDataset(ident="1@b") - map_.set_absolute_time(datetime(2001, 1, 14), datetime(2001, 3, 14)) - map_listB.append(copy.copy(map_)) - map_ = RasterDataset(ident="2@b") - map_.set_absolute_time(datetime(2001, 2, 1), datetime(2001, 4, 1)) - map_listB.append(copy.copy(map_)) - map_ = RasterDataset(ident="3@b") - map_.set_absolute_time(datetime(2001, 2, 14), datetime(2001, 4, 30)) - map_listB.append(copy.copy(map_)) - map_ = RasterDataset(ident="4@b") - map_.set_absolute_time(datetime(2001, 4, 2), datetime(2001, 4, 30)) - map_listB.append(copy.copy(map_)) - map_ = RasterDataset(ident="5@b") - map_.set_absolute_time(datetime(2001, 5, 1), datetime(2001, 5, 14)) - map_listB.append(copy.copy(map_)) - - tb = SpatioTemporalTopologyBuilder() - tb.build(map_listB) - - # Probing some relations - if map_listB[0].get_overlapped()[0] != map_listB[1]: - core.fatal("Error building temporal topology") - if map_listB[0].get_overlapped()[1] != map_listB[2]: - core.fatal("Error building temporal topology") - if map_listB[2].get_contains()[0] != map_listB[3]: - core.fatal("Error building temporal topology") - if map_listB[3].get_during()[0] != map_listB[2]: - core.fatal("Error building temporal topology") - - count = 0 - for map_ in tb: - print("[%s]" % (map_.get_map_id())) - map_.print_topology_shell_info() - if map_.get_id() != map_listB[count].get_id(): - core.fatal( - "Error building temporal topology <%s> != <%s>" - % (map_.get_id(), map_listB[count].get_id()) - ) - count += 1 - - tb = SpatioTemporalTopologyBuilder() - tb.build(map_listA, map_listB) - - count = 0 - for map_ in tb: - print("[%s]" % (map_.get_map_id())) - map_.print_topology_shell_info() - if map_.get_id() != map_listA[count].get_id(): - core.fatal( - "Error building temporal topology <%s> != <%s>" - % (map_.get_id(), map_listA[count].get_id()) - ) - count += 1 - - count = 0 - for map_ in map_listB: - print("[%s]" % (map_.get_map_id())) - map_.print_topology_shell_info() - - # Probing some relations - if map_listA[3].get_follows()[0] != map_listB[1]: - core.fatal("Error building temporal topology") - if map_listA[3].get_precedes()[0] != map_listB[4]: - core.fatal("Error building temporal topology") - if map_listA[3].get_overlaps()[0] != map_listB[2]: - core.fatal("Error building temporal topology") - if map_listA[3].get_contains()[0] != map_listB[3]: - core.fatal("Error building temporal topology") - - if map_listA[2].get_during()[0] != map_listB[1]: - core.fatal("Error building temporal topology") - if map_listA[2].get_during()[1] != map_listB[2]: - core.fatal("Error building temporal topology") - - -############################################################################### - - -def test_map_list_sorting() -> None: - map_list = [] - - map_ = RasterDataset(ident="1@a") - map_.set_absolute_time(datetime(2001, 2, 1), datetime(2001, 3, 1)) - map_list.append(copy.copy(map_)) - map_ = RasterDataset(ident="2@a") - map_.set_absolute_time(datetime(2001, 1, 1), datetime(2001, 2, 1)) - map_list.append(copy.copy(map_)) - map_ = RasterDataset(ident="3@a") - map_.set_absolute_time(datetime(2001, 3, 1), datetime(2001, 4, 1)) - map_list.append(copy.copy(map_)) - - print("Original") - for map_ in map_list: - print( - map_.get_temporal_extent_as_tuple()[0], - map_.get_temporal_extent_as_tuple()[1], - ) - print("Sorted by start time") - new_list = sorted(map_list, key=AbstractDatasetComparisonKeyStartTime) - for map_ in new_list: - print( - map_.get_temporal_extent_as_tuple()[0], - map_.get_temporal_extent_as_tuple()[1], - ) - - if new_list[0] != map_list[1]: - core.fatal("Sorting by start time failed") - if new_list[1] != map_list[0]: - core.fatal("Sorting by start time failed") - if new_list[2] != map_list[2]: - core.fatal("Sorting by start time failed") - - print("Sorted by end time") - new_list = sorted(map_list, key=AbstractDatasetComparisonKeyEndTime) - for map_ in new_list: - print( - map_.get_temporal_extent_as_tuple()[0], - map_.get_temporal_extent_as_tuple()[1], - ) - - if new_list[0] != map_list[1]: - core.fatal("Sorting by end time failed") - if new_list[1] != map_list[0]: - core.fatal("Sorting by end time failed") - if new_list[2] != map_list[2]: - core.fatal("Sorting by end time failed") - - -############################################################################### - - -def test_1d_rtree() -> None: - """Testing the rtree ctypes wrapper""" - - tree = rtree.RTreeCreateTree(-1, 0, 1) - - for i in range(10): - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect1D(rect, tree, float(i - 2), float(i + 2)) - rtree.RTreeInsertRect(rect, i + 1, tree) - - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect1D(rect, tree, 2.0, 7.0) - - list_ = gis.ilist() - - num = vector.RTreeSearch2(tree, rect, byref(list_)) - - rtree.RTreeFreeRect(rect) - - # print rectangle ids - print("Number of overlapping rectangles", num) - for i in range(list_.n_values): - print("id", list_.value[i]) - - rtree.RTreeDestroyTree(tree) - - -############################################################################### - - -def test_2d_rtree() -> None: - """Testing the rtree ctypes wrapper""" - - tree = rtree.RTreeCreateTree(-1, 0, 2) - - for i in range(10): - rect = rtree.RTreeAllocRect(tree) - - rtree.RTreeSetRect2D( - rect, tree, float(i - 2), float(i + 2), float(i - 2), float(i + 2) - ) - rtree.RTreeInsertRect(rect, i + 1, tree) - - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect2D(rect, tree, 2.0, 7.0, 2.0, 7.0) - - list_ = gis.ilist() - - num = vector.RTreeSearch2(tree, rect, byref(list_)) - rtree.RTreeFreeRect(rect) - - # print rectangle ids - print("Number of overlapping rectangles", num) - for i in range(list_.n_values): - print("id", list_.value[i]) - - rtree.RTreeDestroyTree(tree) - - -############################################################################### - - -def test_3d_rtree() -> None: - """Testing the rtree ctypes wrapper""" - - tree = rtree.RTreeCreateTree(-1, 0, 3) - - for i in range(10): - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect3D( - rect, - tree, - float(i - 2), - float(i + 2), - float(i - 2), - float(i + 2), - float(i - 2), - float(i + 2), - ) - rtree.RTreeInsertRect(rect, i + 1, tree) - print(i + 1) - rtree.RTreePrintRect(rect, 1, tree) - - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect3D(rect, tree, 2.0, 7.0, 2.0, 7.0, 2.0, 7.0) - print("Select") - rtree.RTreePrintRect(rect, 1, tree) - - list_ = gis.ilist() - - num = vector.RTreeSearch2(tree, rect, byref(list_)) - rtree.RTreeFreeRect(rect) - - # print rectangle ids - print("Number of overlapping rectangles", num) - for i in range(list_.n_values): - print("id", list_.value[i]) - - rtree.RTreeDestroyTree(tree) - - -############################################################################### - - -def test_4d_rtree() -> None: - """Testing the rtree ctypes wrapper""" - - tree = rtree.RTreeCreateTree(-1, 0, 4) - - for i in range(10): - # Allocate the boundary - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect4D( - rect, - tree, - float(i - 2), - float(i + 2), - float(i - 2), - float(i + 2), - float(i - 2), - float(i + 2), - float(i - 2), - float(i + 2), - ) - rtree.RTreeInsertRect(rect, i + 1, tree) - - rect = rtree.RTreeAllocRect(tree) - rtree.RTreeSetRect4D(rect, tree, 2.0, 7.0, 2.0, 7.0, 2.0, 7.0, 2.0, 7.0) - - list_ = gis.ilist() - - num = vector.RTreeSearch2(tree, rect, byref(list_)) - - rtree.RTreeFreeRect(rect) - - # print rectangle ids - print("Number of overlapping rectangles", num) - for i in range(list_.n_values): - print("id", list_.value[i]) - - rtree.RTreeDestroyTree(tree) - - -############################################################################### - -if __name__ == "__main__": - init() - test_increment_datetime_by_string() - test_adjust_datetime_to_granularity() - test_spatial_extent_intersection() - test_compute_absolute_time_granularity() - test_spatial_extent_intersection() - test_spatial_relations() - test_temporal_topology_builder() - test_map_list_sorting() - test_1d_rtree() - test_2d_rtree() - test_3d_rtree() - test_4d_rtree()