|
28 | 28 | import rasterio.transform |
29 | 29 | import shapely |
30 | 30 | from cartopy.io import shapereader |
| 31 | +from pyproj import Transformer |
31 | 32 | from pyproj.crs import CRS as PCRS |
32 | 33 | from rasterio import Affine |
33 | 34 | from rasterio.crs import CRS as RCRS |
|
49 | 50 | DATA_DIR = CONFIG.util.test_data.dir() |
50 | 51 |
|
51 | 52 |
|
| 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 | + |
52 | 61 | def def_input_values(): |
53 | 62 | """Default input coordinates and centroids values""" |
54 | 63 | # Load exposures coordinates from demo entity file |
@@ -248,33 +257,33 @@ def test_get_unit_degree(self): |
248 | 257 | """Test with a geodetic CRS in degree (EPSG:4326).""" |
249 | 258 | crs = "EPSG:4326" |
250 | 259 | gdf = self.create_mock_gdf(crs) |
251 | | - unit = u_coord.get_crs_unit(gdf) |
| 260 | + unit = u_coord.get_crs_unit(gdf.crs) |
252 | 261 | self.assertEqual(unit, "degree", "Expected unit 'degree' for geodetic CRS.") |
253 | 262 |
|
254 | 263 | def test_get_unit_projected_m(self): |
255 | 264 | """Test with a projected CRS in m (EPSG:3857).""" |
256 | 265 | crs = "EPSG:3857" |
257 | 266 | 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.") |
260 | 269 |
|
261 | 270 | def test_get_unit_projected_km(self): |
262 | 271 | """Test with a projected CRS in km (EPSG:22300).""" |
263 | 272 | crs = "EPSG:22300" |
264 | 273 | 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 | + ) |
267 | 278 |
|
268 | 279 | def test_get_unit_invalid_crs(self): |
269 | 280 | """Test with an invalid CRS.""" |
270 | 281 | crs = "EPSG:8189" |
271 | 282 | 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 | + ) |
278 | 287 |
|
279 | 288 |
|
280 | 289 | class TestDistance(unittest.TestCase): |
@@ -1354,12 +1363,11 @@ def setUp_match_coordinates(self): |
1354 | 1363 | # Set up mock data for tests |
1355 | 1364 | # note that the base coordinates are in lat/lon |
1356 | 1365 | 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)] |
1358 | 1367 | ) |
1359 | 1368 | self.coords_to_assign = np.array( |
1360 | 1369 | [(2.1, 3), (0, 0), (0, 2), (0.9, 1.0), (0, -179.9)] |
1361 | 1370 | ) |
1362 | | - # test with different unit |
1363 | 1371 | self.expected_results = [ |
1364 | 1372 | # test with different thresholds (in degree) |
1365 | 1373 | (2, [2, 1, 2, 0, 3, 1, 4]), |
@@ -1401,62 +1409,108 @@ def test_match_coordinates(self): |
1401 | 1409 | ) |
1402 | 1410 | np.testing.assert_array_equal(assigned_idx, result) |
1403 | 1411 |
|
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""" |
1406 | 1414 | 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 |
1412 | 1453 | ) |
| 1454 | + np.testing.assert_array_equal(assigned_idx, result[:-1]) |
1413 | 1455 |
|
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""" |
1416 | 1458 | 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 |
1425 | 1470 | ) |
| 1471 | + np.testing.assert_array_equal(assigned_idx, result[:-1]) |
1426 | 1472 |
|
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""" |
1429 | 1475 | 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 |
1438 | 1487 | ) |
| 1488 | + np.testing.assert_array_equal(assigned_idx, result[:-1]) |
1439 | 1489 |
|
1440 | | - def antimeridian_warning_invalid_unit(self): |
| 1490 | + def test_antimeridian_warning_invalid_range(self): |
1441 | 1491 | """Check that a warning is raised when coords are |
1442 | 1492 | non-degree and check_antimeridian is True""" |
1443 | 1493 |
|
1444 | 1494 | self.setUp_match_coordinates() |
1445 | 1495 | coords_to_assign = np.deg2rad(self.coords_to_assign) * EARTH_RADIUS_KM # to km |
1446 | 1496 | coords = np.deg2rad(self.coords) * EARTH_RADIUS_KM |
1447 | 1497 |
|
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) |
1458 | 1500 |
|
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 | + ) |
1460 | 1514 |
|
1461 | 1515 |
|
1462 | 1516 | class TestGetGeodata(unittest.TestCase): |
|
0 commit comments