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/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/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/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", + ] 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()