Skip to content

Commit bd87a0f

Browse files
author
Chahan Kropf
committed
Add test for projected crs
1 parent 82b27e0 commit bd87a0f

File tree

1 file changed

+106
-52
lines changed

1 file changed

+106
-52
lines changed

climada/util/test/test_coordinates.py

Lines changed: 106 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import rasterio.transform
2929
import shapely
3030
from cartopy.io import shapereader
31+
from pyproj import Transformer
3132
from pyproj.crs import CRS as PCRS
3233
from rasterio import Affine
3334
from rasterio.crs import CRS as RCRS
@@ -49,6 +50,14 @@
4950
DATA_DIR = CONFIG.util.test_data.dir()
5051

5152

53+
def convert_coordinates(coords_list, source_crs, target_crs):
54+
transformer = Transformer.from_crs(source_crs, target_crs, always_xy=True)
55+
converted_coords = np.array(
56+
[transformer.transform(lon, lat) for lon, lat in coords_list]
57+
)
58+
return converted_coords
59+
60+
5261
def def_input_values():
5362
"""Default input coordinates and centroids values"""
5463
# Load exposures coordinates from demo entity file
@@ -248,33 +257,33 @@ def test_get_unit_degree(self):
248257
"""Test with a geodetic CRS in degree (EPSG:4326)."""
249258
crs = "EPSG:4326"
250259
gdf = self.create_mock_gdf(crs)
251-
unit = u_coord.get_crs_unit(gdf)
260+
unit = u_coord.get_crs_unit(gdf.crs)
252261
self.assertEqual(unit, "degree", "Expected unit 'degree' for geodetic CRS.")
253262

254263
def test_get_unit_projected_m(self):
255264
"""Test with a projected CRS in m (EPSG:3857)."""
256265
crs = "EPSG:3857"
257266
gdf = self.create_mock_gdf(crs)
258-
unit = u_coord.get_crs_unit(gdf)
259-
self.assertEqual(unit, "m", "Expected unit 'm' for projected CRS.")
267+
unit = u_coord.get_crs_unit(gdf.crs)
268+
self.assertEqual(unit, "metre", "Expected unit 'metre' for projected CRS.")
260269

261270
def test_get_unit_projected_km(self):
262271
"""Test with a projected CRS in km (EPSG:22300)."""
263272
crs = "EPSG:22300"
264273
gdf = self.create_mock_gdf(crs)
265-
unit = u_coord.get_crs_unit(gdf)
266-
self.assertEqual(unit, "km", "Expected unit 'km' for projected CRS.")
274+
unit = u_coord.get_crs_unit(gdf.crs)
275+
self.assertEqual(
276+
unit, "kilometre", "Expected unit 'kilometre' for projected CRS."
277+
)
267278

268279
def test_get_unit_invalid_crs(self):
269280
"""Test with an invalid CRS."""
270281
crs = "EPSG:8189"
271282
gdf = self.create_mock_gdf(crs)
272-
with self.assertRaises(ValueError) as cm:
273-
u_coord.get_crs_unit(gdf)
274-
self.assertIn(
275-
"Unknown unit: US survey foot. Please provide a crs that has a unit of 'degree', 'metre' or 'kilometre'.",
276-
str(cm.exception),
277-
)
283+
unit = u_coord.get_crs_unit(gdf.crs)
284+
self.assertEqual(
285+
unit, "US survey foot", "Expected unit 'US survey foot' for projected CRS."
286+
)
278287

279288

280289
class TestDistance(unittest.TestCase):
@@ -1354,12 +1363,11 @@ def setUp_match_coordinates(self):
13541363
# Set up mock data for tests
13551364
# note that the base coordinates are in lat/lon
13561365
self.coords = np.array(
1357-
[(0.2, 2), (0, 0), (0, 2), (2.1, 3), (0.95, 1), (-1, 1), (0, 179.9)]
1366+
[(0.2, 2), (0, 0), (0, 2), (2.1, 3), (0.95, 1), (-0.75, 0.75), (0, 179.9)]
13581367
)
13591368
self.coords_to_assign = np.array(
13601369
[(2.1, 3), (0, 0), (0, 2), (0.9, 1.0), (0, -179.9)]
13611370
)
1362-
# test with different unit
13631371
self.expected_results = [
13641372
# test with different thresholds (in degree)
13651373
(2, [2, 1, 2, 0, 3, 1, 4]),
@@ -1401,62 +1409,108 @@ def test_match_coordinates(self):
14011409
)
14021410
np.testing.assert_array_equal(assigned_idx, result)
14031411

1404-
def test_match_coordinates_invalid_unit(self):
1405-
"""Test match_coordinates with invalid unit"""
1412+
def test_match_coordinates_degree_crs(self):
1413+
"""Test match_coordinates function"""
14061414
self.setUp_match_coordinates()
1407-
coords_to_assign = np.deg2rad(self.coords_to_assign)
1408-
coords = np.deg2rad(self.coords)
1409-
with self.assertRaises(ValueError):
1410-
u_coord.match_coordinates(
1411-
coords, coords_to_assign, "foo", u_coord.NEAREST_NEIGHBOR_THRESHOLD
1415+
for distance in ["euclidean", "haversine", "approx"]:
1416+
# make sure that it works for both float32 and float64
1417+
crs = "EPSG:4258" # degrees, Europe
1418+
for thresh, result in self.expected_results:
1419+
assigned_idx = u_coord.match_coordinates(
1420+
convert_coordinates(self.coords, DEF_CRS, crs),
1421+
convert_coordinates(self.coords_to_assign, DEF_CRS, crs),
1422+
distance=distance,
1423+
crs=crs,
1424+
threshold=thresh,
1425+
)
1426+
np.testing.assert_array_equal(assigned_idx, result)
1427+
1428+
crs = "EPSG:4269" # degrees NA
1429+
for thresh, result in self.expected_results:
1430+
assigned_idx = u_coord.match_coordinates(
1431+
convert_coordinates(self.coords, DEF_CRS, crs),
1432+
convert_coordinates(self.coords_to_assign, DEF_CRS, crs),
1433+
distance=distance,
1434+
crs=crs,
1435+
threshold=thresh,
1436+
)
1437+
np.testing.assert_array_equal(assigned_idx, result)
1438+
1439+
def test_match_coordinates_projected_foot_crs(self):
1440+
"""Test match_coordinates function"""
1441+
self.setUp_match_coordinates()
1442+
distance = "euclidean"
1443+
crs = "EPSG:2227" # US foot
1444+
for thresh, result in self.expected_results:
1445+
assigned_idx = u_coord.match_coordinates(
1446+
convert_coordinates(self.coords, DEF_CRS, crs)[
1447+
:-1
1448+
], # last coordinate out of crs range
1449+
convert_coordinates(self.coords_to_assign, DEF_CRS, crs)[:-1],
1450+
distance=distance,
1451+
crs=crs,
1452+
threshold=u_coord.degree_to_km(thresh) * 3280, # US foot
14121453
)
1454+
np.testing.assert_array_equal(assigned_idx, result[:-1])
14131455

1414-
def test_nearest_neighbor_approx_invalid_unit(self):
1415-
"""Test approx nearest neighbor with invalid unit"""
1456+
def test_match_coordinates_projected_meters_crs(self):
1457+
"""Test match_coordinates function"""
14161458
self.setUp_match_coordinates()
1417-
coords_to_assign = np.deg2rad(self.coords_to_assign)
1418-
coords = np.deg2rad(self.coords)
1419-
with self.assertRaises(ValueError):
1420-
u_coord._nearest_neighbor_approx(
1421-
coords_to_assign,
1422-
coords,
1423-
"km",
1424-
u_coord.NEAREST_NEIGHBOR_THRESHOLD,
1459+
distance = "euclidean"
1460+
crs = "EPSG:4087" # meters
1461+
for thresh, result in self.expected_results:
1462+
assigned_idx = u_coord.match_coordinates(
1463+
convert_coordinates(self.coords, DEF_CRS, crs)[
1464+
:-1
1465+
], # last coordinate out of crs range
1466+
convert_coordinates(self.coords_to_assign, DEF_CRS, crs)[:-1],
1467+
distance=distance,
1468+
crs=crs,
1469+
threshold=u_coord.degree_to_km(thresh) * 1000, # meters
14251470
)
1471+
np.testing.assert_array_equal(assigned_idx, result[:-1])
14261472

1427-
def test_nearest_neighbor_haversine_invalid_unit(self):
1428-
"""Test haversine nearest neighbor with invalid unit"""
1473+
def test_match_coordinates_projected_meters2_crs(self):
1474+
"""Test match_coordinates function"""
14291475
self.setUp_match_coordinates()
1430-
coords_to_assign = np.deg2rad(self.coords_to_assign)
1431-
coords = np.deg2rad(self.coords)
1432-
with self.assertRaises(ValueError):
1433-
u_coord._nearest_neighbor_haversine(
1434-
coords_to_assign,
1435-
coords,
1436-
"km",
1437-
u_coord.NEAREST_NEIGHBOR_THRESHOLD,
1476+
distance = "euclidean"
1477+
crs = "EPSG:3857" # meters
1478+
for thresh, result in self.expected_results:
1479+
assigned_idx = u_coord.match_coordinates(
1480+
convert_coordinates(self.coords, DEF_CRS, crs)[
1481+
:-1
1482+
], # last coordinate out of crs range
1483+
convert_coordinates(self.coords_to_assign, DEF_CRS, crs)[:-1],
1484+
distance=distance,
1485+
crs=crs,
1486+
threshold=u_coord.degree_to_km(thresh) * 1000, # meters
14381487
)
1488+
np.testing.assert_array_equal(assigned_idx, result[:-1])
14391489

1440-
def antimeridian_warning_invalid_unit(self):
1490+
def test_antimeridian_warning_invalid_range(self):
14411491
"""Check that a warning is raised when coords are
14421492
non-degree and check_antimeridian is True"""
14431493

14441494
self.setUp_match_coordinates()
14451495
coords_to_assign = np.deg2rad(self.coords_to_assign) * EARTH_RADIUS_KM # to km
14461496
coords = np.deg2rad(self.coords) * EARTH_RADIUS_KM
14471497

1448-
with self.assertLogs("climada.util.coordinates", level="WARNING") as cm:
1449-
neighbors = u_coord.match_coordinates(
1450-
coords_to_assign, coords, check_antimeridian=True
1451-
)
1452-
self.assertIn(
1453-
"Handling of antimeridian crossing is not implemented for non-degree"
1454-
" coordinates ('check_antimeridian' has been set to False). Please use"
1455-
" degree-based coordinates system if you want to enable antimeridian crossing.",
1456-
cm.output[1],
1457-
)
1498+
with self.assertRaises(ValueError):
1499+
u_coord.match_coordinates(coords_to_assign, coords, check_antimeridian=True)
14581500

1459-
np.testing.assert_array_equal(neighbors, self.data_ref_antimeridian())
1501+
def test_error_projected_crs_haversine_approx(self):
1502+
"""Check that a warning is raised when coords are
1503+
non-degree and check_antimeridian is True"""
1504+
1505+
self.setUp_match_coordinates()
1506+
crs = "EPSG:3857"
1507+
coords_to_assign = convert_coordinates(self.coords_to_assign, DEF_CRS, crs)
1508+
coords = convert_coordinates(self.coords, DEF_CRS, crs)
1509+
for distance in ["haversine", "approx"]:
1510+
with self.assertRaises(ValueError):
1511+
u_coord.match_coordinates(
1512+
coords_to_assign, coords, distance=distance, crs=crs
1513+
)
14601514

14611515

14621516
class TestGetGeodata(unittest.TestCase):

0 commit comments

Comments
 (0)