From 77dbdd248e6077827dd03ae83482dd3ccc2261b8 Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Wed, 27 Nov 2024 09:57:00 +0100 Subject: [PATCH 1/7] added functions and tests --- climada/util/coordinates.py | 98 +++++++++++++++++++++++++++ climada/util/test/test_coordinates.py | 57 ++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index cec74b512c..a587cbe98d 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -1687,6 +1687,104 @@ def _ensure_utf8(val): return admin1_info, admin1_shapes +def boundsNESW_from_global(): + """ + Return global NESW bounds in EPSG 4326 + + Returns + ------- + list: + The calculated bounding box as [north, east, south, west] in EPSG 4326 + """ + return [90, 180, -90, -180] + + +def boundsNESW_from_country_codes(country_codes, rel_margin=0.2): + """ + Return NESW bounds in EPSG 4326 for the combined area defined by given country ISO codes. + + Parameters + ---------- + country_codes : list + A list of ISO country codes (e.g.,['ITA'], ['ITA', 'CHE']). + rel_margin : float + A relative margin to extend the bounding box in all directions. Default is 0.2. + + Returns + ------- + list: + The calculated bounding box as [north, east, south, west] in EPSG 4326 + """ + [north, east, south, west] = [-90, -180, 90, 180] + + # loop through ISO codes + for iso in country_codes: + geo = get_country_geometries(iso).to_crs(epsg=4326) + iso_west, iso_south, iso_east, iso_north = geo.total_bounds + if np.any(np.isnan([iso_west, iso_south, iso_east, iso_north])): + LOGGER.warning( + f"ISO code '{iso}' not recognized. This region will not be included." + ) + continue + + north = max(north, iso_north) + east = max(east, iso_east) + south = min(south, iso_south) + west = min(west, iso_west) + + # no countries recognized + if [north, east, south, west] == [-90, -180, 90, 180]: + raise Exception("No ISO code was recognized.") + + # add relative margin + lat_margin = rel_margin * (north - south) + lon_margin = rel_margin * (east - west) + north = min(north + lat_margin, 90) + east = min(east + lon_margin, 180) + south = max(south - lat_margin, -90) + west = max(west - lon_margin, -180) + + return [north, east, south, west] + + +def boundsNESW_from_NESW(*, north, east, south, west, rel_margin=0.0): + """ + Return NESW bounds in EPSG 4326 with relative margin from given NESW values in EPSG 4326. + + Parameters + ---------- + north : (float, int) + Maximal latitude in EPSG 4326. + east : (float, int) + Maximal longitute in EPSG 4326. + south : (float, int) + Minimal latitude in EPSG 4326. + west : (float, int) + Minimal longitude in EPSG 4326. + rel_margin : float + A relative margin to extend the bounding box in all directions. Default is 0.2. + + Returns + ------- + list: + The calculated bounding box as [north, east, south, west] in EPSG 4326 + """ + + # simple bounds check + if not ((90 >= north > south >= -90) and (180 >= east > west >= -180)): + raise ValueError("Given bounds are not in standard order or standard bounds") + + # add relative margin + lat_margin = rel_margin * (north - south) + lon_margin = rel_margin * (east - west) + north = min(north + lat_margin, 90) + east = min(east + lon_margin, 180) + south = max(south - lat_margin, -90) + west = max(west - lon_margin, -180) + + return [north, east, south, west] + + def get_admin1_geometries(countries): """ return geometries, names and codes of admin 1 regions in given countries diff --git a/climada/util/test/test_coordinates.py b/climada/util/test/test_coordinates.py index 50d5a8073e..02c5682945 100644 --- a/climada/util/test/test_coordinates.py +++ b/climada/util/test/test_coordinates.py @@ -2294,6 +2294,62 @@ def test_mask_raster_with_geometry(self): ) +class TestBoundsFromUserInput(unittest.TestCase): + """Unit tests for the bounds_from_user_input function.""" + + def test_boundsNESW_from_global(self): + """Test for 'global' area selection.""" + result = u_coord.boundsNESW_from_global() + expected = [90, 180, -90, -180] + np.testing.assert_almost_equal(result, expected) + + def test_boundsNESW_from_country_codes(self): + """Test for a list of ISO country codes.""" + result = u_coord.boundsNESW_from_country_codes( + ["ITA"], rel_margin=0.2 + ) # Testing with Italy (ITA) + # Real expected bounds for Italy (calculated or manually known) + expected = [ + 49.404409157600064, + 20.900365510000075, + 33.170049669400036, + 4.219788779000066, + ] # Italy's bounding box + + np.testing.assert_array_almost_equal(result, expected, decimal=4) + + def test_bounding_box(self): + """Test for bounding box input with margin applied.""" + [north, east, south, west] = [50, -100, 30, -120] + result = u_coord.boundsNESW_from_NESW( + north=north, south=south, west=west, east=east, rel_margin=0.1 + ) + expected = [ + 50 + 2, + -100 + 2, + 30 - 2, + -120 - 2, + ] # Apply margin calculation + np.testing.assert_array_almost_equal(result, expected) + + def test_invalid_input_string(self): + """Test for invalid string input.""" + with self.assertRaises(Exception): + u_coord.boundsNESW_from_country_codes("DEU") + + def test_empty_input(self): + """Test for empty input.""" + with self.assertRaises(Exception): + u_coord.boundsNESW_from_country_codes([]) + + def test_invalid_coordinate_input(self): + """Test for str in coordinates input input.""" + with self.assertRaises(ValueError): + u_coord.boundsNESW_from_NESW(north=40, south=50, east=30, west=10) + with self.assertRaises(TypeError): + u_coord.boundsNESW_from_NESW(north=40, south="20", east=30, west=10) + + # Execute Tests if __name__ == "__main__": TESTS = unittest.TestLoader().loadTestsFromTestCase(TestFunc) @@ -2302,4 +2358,5 @@ def test_mask_raster_with_geometry(self): TESTS.addTests(unittest.TestLoader().loadTestsFromTestCase(TestRasterMeta)) TESTS.addTests(unittest.TestLoader().loadTestsFromTestCase(TestRasterIO)) TESTS.addTests(unittest.TestLoader().loadTestsFromTestCase(TestDistance)) + TESTS.addTests(unittest.TestLoader().loadTestsFromTestCase(TestBoundsFromUserInput)) unittest.TextTestRunner(verbosity=2).run(TESTS) From 2bfe421d18e733db4c0dcc8d0a9923ab91b437e5 Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Fri, 29 Nov 2024 17:35:32 +0100 Subject: [PATCH 2/7] adapted functions and order of outputs --- climada/util/coordinates.py | 126 ++++++++++++-------------- climada/util/test/test_coordinates.py | 77 +++++++++------- 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index a587cbe98d..27a4642d1f 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -783,12 +783,22 @@ def get_country_geometries( if country_names: if isinstance(country_names, str): country_names = [country_names] + + # print warning if ISO code not recognized + for country_name in country_names: + if not country_name in nat_earth[["ISO_A3", "WB_A3", "ADM0_A3"]].values: + LOGGER.warning(f"ISO code {country_name} not recognized.") + country_mask = np.isin( nat_earth[["ISO_A3", "WB_A3", "ADM0_A3"]].values, country_names, ).any(axis=1) out = out[country_mask] + # exit with Value error if no country code was recognized + if out.size == 0: + raise ValueError(f"None of the given country codes were regocnized.") + if extent: if extent[1] - extent[0] > 360: raise ValueError( @@ -1687,102 +1697,84 @@ def _ensure_utf8(val): return admin1_info, admin1_shapes -def boundsNESW_from_global(): +def global_bounding_box(): """ - Return global NESW bounds in EPSG 4326 + Return global bounds in EPSG 4326 Returns ------- - list: - The calculated bounding box as [north, east, south, west] in EPSG 4326 + tuple: + The global bounding box as (min_lon, min_lat, max_lon, max_lat) """ - return [90, 180, -90, -180] + return (-180, -90, 180, 90) -def boundsNESW_from_country_codes(country_codes, rel_margin=0.2): +def get_country_bounding_box(country_names, buffer=1.0): """ - Return NESW bounds in EPSG 4326 for the combined area defined by given country ISO codes. + Return bounding box in EPSG 4326 containing given countries. Parameters ---------- - country_codes : list - A list of ISO country codes (e.g.,['ITA'], ['ITA', 'CHE']). - rel_margin : float - A relative margin to extend the bounding box in all directions. Default is 0.2. + country_names : list or str + list with ISO 3166 alpha-3 codes of countries, e.g ['ZWE', 'GBR', 'VNM', 'UZB'] + buffer : float, optional + Buffer to add to both sides of the bounding box. Default: 1.0. Returns ------- - list: - The calculated bounding box as [north, east, south, west] in EPSG 4326 - """ - [north, east, south, west] = [-90, -180, 90, 180] - - # loop through ISO codes - for iso in country_codes: - geo = get_country_geometries(iso).to_crs(epsg=4326) - iso_west, iso_south, iso_east, iso_north = geo.total_bounds - if np.any(np.isnan([iso_west, iso_south, iso_east, iso_north])): - LOGGER.warning( - f"ISO code '{iso}' not recognized. This region will not be included." - ) - continue - - north = max(north, iso_north) - east = max(east, iso_east) - south = min(south, iso_south) - west = min(west, iso_west) - - # no countries recognized - if [north, east, south, west] == [-90, -180, 90, 180]: - raise Exception("No ISO code was recognized.") + tuple + The bounding box containing all given coutries as (min_lon, min_lat, max_lon, max_lat) + """ - # add relative margin - lat_margin = rel_margin * (north - south) - lon_margin = rel_margin * (east - west) - north = min(north + lat_margin, 90) - east = min(east + lon_margin, 180) - south = max(south - lat_margin, -90) - west = max(west - lon_margin, -180) + country_geometry = get_country_geometries(country_names).geometry + longitudes, latitudes = [], [] + for multipolygon in country_geometry: + for polygon in multipolygon.geoms: # Loop through each polygon + for coord in polygon.exterior.coords: # Extract exterior coordinates + longitudes.append(coord[0]) + latitudes.append(coord[1]) - return [north, east, south, west] + return latlon_bounds(np.array(latitudes), np.array(longitudes), buffer=buffer) -def boundsNESW_from_NESW(*, north, east, south, west, rel_margin=0.0): +def bounds_from_cardinal_bounds(*, northern, eastern, western, southern): """ - Return NESW bounds in EPSG 4326 with relative margin from given NESW values in EPSG 4326. + Return and normalize bounding box in EPSG 4326 from given cardinal bounds. Parameters ---------- - north : (float, int) - Maximal latitude in EPSG 4326. - east : (float, int) - Maximal longitute in EPSG 4326. - south : (float, int) - Minimal latitude in EPSG 4326. - west : (float, int) - Minimal longitude in EPSG 4326. - rel_margin : float - A relative margin to extend the bounding box in all directions. Default is 0.2. + northern : (int, float) + Northern boundary of bounding box + eastern : (int, float) + Eastern boundary of bounding box + western : (int, float) + Western boundary of bounding box + southern : (int, float) + Southern boundary of bounding box Returns ------- - list: - The calculated bounding box as [north, east, south, west] in EPSG 4326 + tuple + The resulting normalized bounding box (min_lon, min_lat, max_lon, max_lat) with -180 <= min_lon < max_lon < 540 + """ - # simple bounds check - if not ((90 >= north > south >= -90) and (180 >= east > west >= -180)): - raise ValueError("Given bounds are not in standard order or standard bounds") + # latitude bounds check + if not ((90 >= northern > southern >= -90)): + raise ValueError( + "Given northern bound is below given southern bound or out of bounds" + ) - # add relative margin - lat_margin = rel_margin * (north - south) - lon_margin = rel_margin * (east - west) - north = min(north + lat_margin, 90) - east = min(east + lon_margin, 180) - south = max(south - lat_margin, -90) - west = max(west - lon_margin, -180) + if not (360 >= eastern >= -180) or not (360 >= western >= -180): + raise ValueError("Given eastern/western bounds are out of range (-180, 360).") + # order eastern and western coordinates + if western > eastern: + eastern += 360 + if eastern > 360 and western > 180: + eastern -= 360 + western -= 360 - return [north, east, south, west] + return (western, southern, eastern, northern) def get_admin1_geometries(countries): diff --git a/climada/util/test/test_coordinates.py b/climada/util/test/test_coordinates.py index 02c5682945..66d1e10907 100644 --- a/climada/util/test/test_coordinates.py +++ b/climada/util/test/test_coordinates.py @@ -2297,57 +2297,64 @@ def test_mask_raster_with_geometry(self): class TestBoundsFromUserInput(unittest.TestCase): """Unit tests for the bounds_from_user_input function.""" - def test_boundsNESW_from_global(self): + def global_bounding_box(self): """Test for 'global' area selection.""" - result = u_coord.boundsNESW_from_global() - expected = [90, 180, -90, -180] + result = u_coord.global_bounding_box() + expected = (-180, -90, 180, 90) np.testing.assert_almost_equal(result, expected) - def test_boundsNESW_from_country_codes(self): + def test_get_country_bounding_box(self): """Test for a list of ISO country codes.""" - result = u_coord.boundsNESW_from_country_codes( - ["ITA"], rel_margin=0.2 + result = u_coord.get_country_bounding_box( + ["ITA"], buffer=1.0 ) # Testing with Italy (ITA) # Real expected bounds for Italy (calculated or manually known) expected = [ - 49.404409157600064, - 20.900365510000075, - 33.170049669400036, - 4.219788779000066, + 5.6027283120000675, + 34.48924388200004, + 19.517425977000073, + 48.08521494500006, ] # Italy's bounding box np.testing.assert_array_almost_equal(result, expected, decimal=4) - def test_bounding_box(self): - """Test for bounding box input with margin applied.""" - [north, east, south, west] = [50, -100, 30, -120] - result = u_coord.boundsNESW_from_NESW( - north=north, south=south, west=west, east=east, rel_margin=0.1 + def test_bounds_from_cardinal_bounds(self): + """Test for conversion from cardinal bounds to bounds.""" + np.testing.assert_array_almost_equal( + u_coord.bounds_from_cardinal_bounds( + northern=90, southern=-20, eastern=30, western=20 + ), + (20, -20, 30, 90), + ) + np.testing.assert_array_almost_equal( + u_coord.bounds_from_cardinal_bounds( + northern=90, southern=-20, eastern=20, western=30 + ), + (30, -20, 380, 90), + ) + np.testing.assert_array_almost_equal( + u_coord.bounds_from_cardinal_bounds( + northern=90, southern=-20, eastern=170, western=-170 + ), + (-170, -20, 170, 90), + ) + np.testing.assert_array_almost_equal( + u_coord.bounds_from_cardinal_bounds( + northern=90, southern=-20, eastern=-170, western=170 + ), + (170, -20, 190, 90), + ) + np.testing.assert_array_almost_equal( + u_coord.bounds_from_cardinal_bounds( + northern=90, southern=-20, eastern=170, western=175 + ), + (175, -20, 530, 90), ) - expected = [ - 50 + 2, - -100 + 2, - 30 - 2, - -120 - 2, - ] # Apply margin calculation - np.testing.assert_array_almost_equal(result, expected) def test_invalid_input_string(self): """Test for invalid string input.""" with self.assertRaises(Exception): - u_coord.boundsNESW_from_country_codes("DEU") - - def test_empty_input(self): - """Test for empty input.""" - with self.assertRaises(Exception): - u_coord.boundsNESW_from_country_codes([]) - - def test_invalid_coordinate_input(self): - """Test for str in coordinates input input.""" - with self.assertRaises(ValueError): - u_coord.boundsNESW_from_NESW(north=40, south=50, east=30, west=10) - with self.assertRaises(TypeError): - u_coord.boundsNESW_from_NESW(north=40, south="20", east=30, west=10) + u_coord.get_bound("invalid_ISO") # Execute Tests From 7526ab31889ee578e784a60763389572e095ce4f Mon Sep 17 00:00:00 2001 From: Valentin Gebhart <60438839+ValentinGebhart@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:01:50 +0100 Subject: [PATCH 3/7] Apply suggestions from code review Sam Co-authored-by: Samuel Juhel <10011382+spjuhel@users.noreply.github.com> --- climada/util/coordinates.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index 27a4642d1f..b33fd117f4 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -797,7 +797,7 @@ def get_country_geometries( # exit with Value error if no country code was recognized if out.size == 0: - raise ValueError(f"None of the given country codes were regocnized.") + raise ValueError(f"None of the given country codes were recognized.") if extent: if extent[1] - extent[0] > 360: @@ -1765,14 +1765,12 @@ def bounds_from_cardinal_bounds(*, northern, eastern, western, southern): "Given northern bound is below given southern bound or out of bounds" ) - if not (360 >= eastern >= -180) or not (360 >= western >= -180): - raise ValueError("Given eastern/western bounds are out of range (-180, 360).") - # order eastern and western coordinates + eastern = (eastern + 180) % 360 - 180 + western = (western + 180) % 360 - 180 + + # Ensure eastern > western if western > eastern: eastern += 360 - if eastern > 360 and western > 180: - eastern -= 360 - western -= 360 return (western, southern, eastern, northern) From 679c2ec33dab6c623d144dcb66c0a7255b21f75b Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Fri, 13 Dec 2024 12:57:43 +0100 Subject: [PATCH 4/7] added tests from invalid cardinal bounds inputs --- climada/util/test/test_coordinates.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/climada/util/test/test_coordinates.py b/climada/util/test/test_coordinates.py index 66d1e10907..15a85775af 100644 --- a/climada/util/test/test_coordinates.py +++ b/climada/util/test/test_coordinates.py @@ -2316,8 +2316,6 @@ def test_get_country_bounding_box(self): 48.08521494500006, ] # Italy's bounding box - np.testing.assert_array_almost_equal(result, expected, decimal=4) - def test_bounds_from_cardinal_bounds(self): """Test for conversion from cardinal bounds to bounds.""" np.testing.assert_array_almost_equal( @@ -2351,6 +2349,18 @@ def test_bounds_from_cardinal_bounds(self): (175, -20, 530, 90), ) + # some invalid cases + with self.assertRaises(TypeError): + u_coord.bounds_from_cardinal_bounds(southern=-20, eastern=30, western=20) + with self.assertRaises(TypeError): + u_coord.bounds_from_cardinal_bounds([90, -20, 30, 20]) + with self.assertRaises(TypeError): + u_coord.bounds_from_cardinal_bounds(90, -20, 30, 20) + with self.assertRaises(TypeError): + u_coord.bounds_from_cardinal_bounds( + northern="90", southern=-20, eastern=30, western=20 + ) + def test_invalid_input_string(self): """Test for invalid string input.""" with self.assertRaises(Exception): From 8ad36cb8b0efec2a5080a0485f5cbc40ebc4c296 Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Fri, 13 Dec 2024 13:19:43 +0100 Subject: [PATCH 5/7] added ValueError if ISO code not recognized --- climada/util/coordinates.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index b33fd117f4..60b616eb94 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -784,10 +784,10 @@ def get_country_geometries( if isinstance(country_names, str): country_names = [country_names] - # print warning if ISO code not recognized + # raise error if a country name is not recognized for country_name in country_names: if not country_name in nat_earth[["ISO_A3", "WB_A3", "ADM0_A3"]].values: - LOGGER.warning(f"ISO code {country_name} not recognized.") + raise ValueError(f"ISO code {country_name} not recognized.") country_mask = np.isin( nat_earth[["ISO_A3", "WB_A3", "ADM0_A3"]].values, @@ -795,10 +795,6 @@ def get_country_geometries( ).any(axis=1) out = out[country_mask] - # exit with Value error if no country code was recognized - if out.size == 0: - raise ValueError(f"None of the given country codes were recognized.") - if extent: if extent[1] - extent[0] > 360: raise ValueError( @@ -1729,10 +1725,15 @@ def get_country_bounding_box(country_names, buffer=1.0): country_geometry = get_country_geometries(country_names).geometry longitudes, latitudes = [], [] for multipolygon in country_geometry: - for polygon in multipolygon.geoms: # Loop through each polygon + if isinstance(multipolygon, Polygon): # if entry is polygon for coord in polygon.exterior.coords: # Extract exterior coordinates longitudes.append(coord[0]) latitudes.append(coord[1]) + else: # if entry is multipolygon + for polygon in multipolygon.geoms: + for coord in polygon.exterior.coords: # Extract exterior coordinates + longitudes.append(coord[0]) + latitudes.append(coord[1]) return latlon_bounds(np.array(latitudes), np.array(longitudes), buffer=buffer) From 847b906448b179a095d713bddfcb3e153a1ff15f Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Fri, 13 Dec 2024 13:50:13 +0100 Subject: [PATCH 6/7] consistent naming of functions --- climada/util/coordinates.py | 6 ++--- climada/util/test/test_coordinates.py | 39 ++++++++++++++------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/climada/util/coordinates.py b/climada/util/coordinates.py index 60b616eb94..351263e629 100644 --- a/climada/util/coordinates.py +++ b/climada/util/coordinates.py @@ -1693,7 +1693,7 @@ def _ensure_utf8(val): return admin1_info, admin1_shapes -def global_bounding_box(): +def bounding_box_global(): """ Return global bounds in EPSG 4326 @@ -1705,7 +1705,7 @@ def global_bounding_box(): return (-180, -90, 180, 90) -def get_country_bounding_box(country_names, buffer=1.0): +def bounding_box_from_countries(country_names, buffer=1.0): """ Return bounding box in EPSG 4326 containing given countries. @@ -1738,7 +1738,7 @@ def get_country_bounding_box(country_names, buffer=1.0): return latlon_bounds(np.array(latitudes), np.array(longitudes), buffer=buffer) -def bounds_from_cardinal_bounds(*, northern, eastern, western, southern): +def bounding_box_from_cardinal_bounds(*, northern, eastern, western, southern): """ Return and normalize bounding box in EPSG 4326 from given cardinal bounds. diff --git a/climada/util/test/test_coordinates.py b/climada/util/test/test_coordinates.py index 15a85775af..86f95f764d 100644 --- a/climada/util/test/test_coordinates.py +++ b/climada/util/test/test_coordinates.py @@ -2297,15 +2297,15 @@ def test_mask_raster_with_geometry(self): class TestBoundsFromUserInput(unittest.TestCase): """Unit tests for the bounds_from_user_input function.""" - def global_bounding_box(self): + def test_bounding_box_global(self): """Test for 'global' area selection.""" - result = u_coord.global_bounding_box() + result = u_coord.bounding_box_global() expected = (-180, -90, 180, 90) np.testing.assert_almost_equal(result, expected) - def test_get_country_bounding_box(self): + def test_bounding_box_from_countries(self): """Test for a list of ISO country codes.""" - result = u_coord.get_country_bounding_box( + result = u_coord.bounding_box_from_countries( ["ITA"], buffer=1.0 ) # Testing with Italy (ITA) # Real expected bounds for Italy (calculated or manually known) @@ -2316,34 +2316,38 @@ def test_get_country_bounding_box(self): 48.08521494500006, ] # Italy's bounding box - def test_bounds_from_cardinal_bounds(self): + # invalid input + with self.assertRaises(ValueError): + u_coord.bounding_box_from_countries(["invalid_ISO", "DEU"]) + + def test_bounding_box_from_cardinal_bounds(self): """Test for conversion from cardinal bounds to bounds.""" np.testing.assert_array_almost_equal( - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern=90, southern=-20, eastern=30, western=20 ), (20, -20, 30, 90), ) np.testing.assert_array_almost_equal( - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern=90, southern=-20, eastern=20, western=30 ), (30, -20, 380, 90), ) np.testing.assert_array_almost_equal( - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern=90, southern=-20, eastern=170, western=-170 ), (-170, -20, 170, 90), ) np.testing.assert_array_almost_equal( - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern=90, southern=-20, eastern=-170, western=170 ), (170, -20, 190, 90), ) np.testing.assert_array_almost_equal( - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern=90, southern=-20, eastern=170, western=175 ), (175, -20, 530, 90), @@ -2351,21 +2355,18 @@ def test_bounds_from_cardinal_bounds(self): # some invalid cases with self.assertRaises(TypeError): - u_coord.bounds_from_cardinal_bounds(southern=-20, eastern=30, western=20) + u_coord.bounding_box_from_cardinal_bounds( + southern=-20, eastern=30, western=20 + ) with self.assertRaises(TypeError): - u_coord.bounds_from_cardinal_bounds([90, -20, 30, 20]) + u_coord.bounding_box_from_cardinal_bounds([90, -20, 30, 20]) with self.assertRaises(TypeError): - u_coord.bounds_from_cardinal_bounds(90, -20, 30, 20) + u_coord.bounding_box_from_cardinal_bounds(90, -20, 30, 20) with self.assertRaises(TypeError): - u_coord.bounds_from_cardinal_bounds( + u_coord.bounding_box_from_cardinal_bounds( northern="90", southern=-20, eastern=30, western=20 ) - def test_invalid_input_string(self): - """Test for invalid string input.""" - with self.assertRaises(Exception): - u_coord.get_bound("invalid_ISO") - # Execute Tests if __name__ == "__main__": From fa258faa1a56ac7f9864c7dff4ad63924e482804 Mon Sep 17 00:00:00 2001 From: Valentin Gebhart Date: Thu, 19 Dec 2024 08:48:35 +0100 Subject: [PATCH 7/7] updated changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2137b7a4da..1d550c8417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ Code freeze date: YYYY-MM-DD ### Added +- `climada.util.coordinates.bounding_box_global` function [#980](https://github.com/CLIMADA-project/climada_python/pull/980) +- `climada.util.coordinates.bounding_box_from_countries` function [#980](https://github.com/CLIMADA-project/climada_python/pull/980) +- `climada.util.coordinates.bounding_box_from_cardinal_bounds` function [#980](https://github.com/CLIMADA-project/climada_python/pull/980) - `climada.engine.impact.Impact.local_return_period` method [#971](https://github.com/CLIMADA-project/climada_python/pull/971) - `doc.tutorial.climada_util_local_exceedance_values.ipynb` tutorial explaining `Hazard.local_exceedance_intensity`, `Hazard.local_return_period`, `Impact.local_exceedance_impact`, and `Impact.local_return_period` methods [#971](https://github.com/CLIMADA-project/climada_python/pull/971) - `Hazard.local_exceedance_intensity`, `Hazard.local_return_period` and `Impact.local_exceedance_impact`, that all use the `climada.util.interpolation` module [#918](https://github.com/CLIMADA-project/climada_python/pull/918) @@ -28,6 +31,7 @@ Code freeze date: YYYY-MM-DD ### Changed +- `climada.util.coordinates.get_country_geometries` function: Now throwing a ValueError if unregognized ISO country code is given (before, the invalid ISO code was ignored) [#980](https://github.com/CLIMADA-project/climada_python/pull/980) - Improved scaling factors implemented in `climada.hazard.trop_cyclone.apply_climate_scenario_knu` to model the impact of climate changes to tropical cyclones [#734](https://github.com/CLIMADA-project/climada_python/pull/734) - In `climada.util.plot.geo_im_from_array`, NaNs are plotted in gray while cells with no centroid are not plotted [#929](https://github.com/CLIMADA-project/climada_python/pull/929) - Renamed `climada.util.plot.subplots_from_gdf` to `climada.util.plot.plot_from_gdf` [#929](https://github.com/CLIMADA-project/climada_python/pull/929)