|
27 | 27 | grid_files = [gridfile_CSne8, gridfile_geoflow] |
28 | 28 | data_files = [datafile_CSne30, datafile_geoflow] |
29 | 29 |
|
| 30 | +grid_quad_hex = current_path/ "meshfiles" / "ugrid" / "quad-hexagon" / "grid.nc" |
| 31 | +grid_geoflow = current_path/ "meshfiles" / "ugrid" / "geoflow-small" / "grid.nc" |
| 32 | +grid_mpas = current_path/ "meshfiles" / "mpas" / "QU" / "oQU480.231010.nc" |
| 33 | + |
| 34 | + |
| 35 | +# List of grid files to test |
| 36 | +grid_files_latlonBound = [grid_quad_hex, grid_geoflow, gridfile_CSne8, grid_mpas] |
30 | 37 |
|
31 | 38 | class TestAntimeridian(TestCase): |
32 | 39 |
|
@@ -56,6 +63,8 @@ def test_linecollection_execution(self): |
56 | 63 | lines = uxgrid.to_linecollection() |
57 | 64 |
|
58 | 65 |
|
| 66 | + |
| 67 | + |
59 | 68 | class TestPredicate(TestCase): |
60 | 69 |
|
61 | 70 | def test_pole_point_inside_polygon_from_vertice_north(self): |
@@ -366,6 +375,19 @@ def test_extreme_gca_latitude_max(self): |
366 | 375 | expected_max_latitude, |
367 | 376 | delta=ERROR_TOLERANCE) |
368 | 377 |
|
| 378 | + def test_extreme_gca_latitude_max_short(self): |
| 379 | + # Define a great circle arc in 3D space that has a small span |
| 380 | + gca_cart = np.array( [[ 0.65465367, -0.37796447, -0.65465367], [ 0.6652466, -0.33896007, -0.6652466 ]]) |
| 381 | + |
| 382 | + # Calculate the maximum latitude |
| 383 | + max_latitude = ux.grid.arcs.extreme_gca_latitude(gca_cart, 'max') |
| 384 | + |
| 385 | + # Check if the maximum latitude is correct |
| 386 | + expected_max_latitude = self._max_latitude_rad_iterative(gca_cart) |
| 387 | + self.assertAlmostEqual(max_latitude, |
| 388 | + expected_max_latitude, |
| 389 | + delta=ERROR_TOLERANCE) |
| 390 | + |
369 | 391 | def test_extreme_gca_latitude_min(self): |
370 | 392 | # Define a great circle arc that is symmetrical around 0 degrees longitude |
371 | 393 | gca_cart = np.array([ |
@@ -621,6 +643,103 @@ def test_populate_bounds_antimeridian(self): |
621 | 643 | bounds = _populate_face_latlon_bound(face_edges_connectivity_cartesian, face_edges_connectivity_lonlat) |
622 | 644 | nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
623 | 645 |
|
| 646 | + def test_populate_bounds_equator(self): |
| 647 | + # the face is touching the equator |
| 648 | + face_edges_cart = np.array([ |
| 649 | + [[0.99726469, -0.05226443, -0.05226443], [0.99862953, 0.0, -0.05233596]], |
| 650 | + [[0.99862953, 0.0, -0.05233596], [1.0, 0.0, 0.0]], |
| 651 | + [[1.0, 0.0, 0.0], [0.99862953, -0.05233596, 0.0]], |
| 652 | + [[0.99862953, -0.05233596, 0.0], [0.99726469, -0.05226443, -0.05226443]] |
| 653 | + ] |
| 654 | + ) |
| 655 | + # Apply the inverse transformation to get the lat lon coordinates |
| 656 | + face_edges_lonlat = np.array( |
| 657 | + [[_xyz_to_lonlat_rad(*edge[0]), _xyz_to_lonlat_rad(*edge[1])] for edge in face_edges_cart]) |
| 658 | + |
| 659 | + bounds = _populate_face_latlon_bound(face_edges_cart, face_edges_lonlat) |
| 660 | + expected_bounds = np.array([[-0.05235988, 0], [6.23082543, 0]]) |
| 661 | + nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
| 662 | + |
| 663 | + def test_populate_bounds_southSphere(self): |
| 664 | + # The face is near the south pole but doesn't contains the pole |
| 665 | + face_edges_cart = np.array([ |
| 666 | + [[-1.04386773e-01, -5.20500333e-02, -9.93173799e-01], [-1.04528463e-01, -1.28010448e-17, -9.94521895e-01]], |
| 667 | + [[-1.04528463e-01, -1.28010448e-17, -9.94521895e-01], [-5.23359562e-02, -6.40930613e-18, -9.98629535e-01]], |
| 668 | + [[-5.23359562e-02, -6.40930613e-18, -9.98629535e-01], [-5.22644277e-02, -5.22644277e-02, -9.97264689e-01]], |
| 669 | + [[-5.22644277e-02, -5.22644277e-02, -9.97264689e-01], [-1.04386773e-01, -5.20500333e-02, -9.93173799e-01]] |
| 670 | + ]) |
| 671 | + |
| 672 | + # Apply the inverse transformation to get the lat lon coordinates |
| 673 | + face_edges_lonlat = np.array( |
| 674 | + [[_xyz_to_lonlat_rad(*edge[0]), _xyz_to_lonlat_rad(*edge[1])] for edge in face_edges_cart]) |
| 675 | + |
| 676 | + bounds = _populate_face_latlon_bound(face_edges_cart, face_edges_lonlat) |
| 677 | + expected_bounds = np.array([[-1.51843645, -1.45388627], [3.14159265, 3.92699082]]) |
| 678 | + nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
| 679 | + |
| 680 | + def test_populate_bounds_near_pole(self): |
| 681 | + # The face is near the south pole but doesn't contains the pole |
| 682 | + face_edges_cart = np.array([ |
| 683 | + [[3.58367950e-01, 0.00000000e+00, -9.33580426e-01], [3.57939780e-01, 4.88684203e-02, -9.32465008e-01]], |
| 684 | + [[3.57939780e-01, 4.88684203e-02, -9.32465008e-01], [4.06271283e-01, 4.78221112e-02, -9.12500241e-01]], |
| 685 | + [[4.06271283e-01, 4.78221112e-02, -9.12500241e-01], [4.06736643e-01, 2.01762691e-16, -9.13545458e-01]], |
| 686 | + [[4.06736643e-01, 2.01762691e-16, -9.13545458e-01], [3.58367950e-01, 0.00000000e+00, -9.33580426e-01]] |
| 687 | + ]) |
| 688 | + |
| 689 | + # Apply the inverse transformation to get the lat lon coordinates |
| 690 | + face_edges_lonlat = np.array( |
| 691 | + [[_xyz_to_lonlat_rad(*edge[0]), _xyz_to_lonlat_rad(*edge[1])] for edge in face_edges_cart]) |
| 692 | + |
| 693 | + bounds = _populate_face_latlon_bound(face_edges_cart, face_edges_lonlat) |
| 694 | + expected_bounds = np.array([[-1.20427718, -1.14935491], [0,0.13568803]]) |
| 695 | + nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
| 696 | + |
| 697 | + def test_populate_bounds_near_pole2(self): |
| 698 | + # The face is near the south pole but doesn't contains the pole |
| 699 | + face_edges_cart = np.array([ |
| 700 | + [[3.57939780e-01, -4.88684203e-02, -9.32465008e-01], [3.58367950e-01, 0.00000000e+00, -9.33580426e-01]], |
| 701 | + [[3.58367950e-01, 0.00000000e+00, -9.33580426e-01], [4.06736643e-01, 2.01762691e-16, -9.13545458e-01]], |
| 702 | + [[4.06736643e-01, 2.01762691e-16, -9.13545458e-01], [4.06271283e-01, -4.78221112e-02, -9.12500241e-01]], |
| 703 | + [[4.06271283e-01, -4.78221112e-02, -9.12500241e-01], [3.57939780e-01, -4.88684203e-02, -9.32465008e-01]] |
| 704 | + ]) |
| 705 | + |
| 706 | + |
| 707 | + # Apply the inverse transformation to get the lat lon coordinates |
| 708 | + face_edges_lonlat = np.array( |
| 709 | + [[_xyz_to_lonlat_rad(*edge[0]), _xyz_to_lonlat_rad(*edge[1])] for edge in face_edges_cart]) |
| 710 | + |
| 711 | + bounds = _populate_face_latlon_bound(face_edges_cart, face_edges_lonlat) |
| 712 | + expected_bounds = np.array([[-1.20427718, -1.14935491], [6.147497,4.960524e-16]]) |
| 713 | + nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
| 714 | + |
| 715 | + def test_populate_bounds_long_face(self): |
| 716 | + """Test case where one of the face edges is a longitude GCA.""" |
| 717 | + face_edges_cart = np.array([ |
| 718 | + [[9.9999946355819702e-01, -6.7040475551038980e-04, 8.0396590055897832e-04], |
| 719 | + [9.9999439716339111e-01, -3.2541253603994846e-03, -8.0110825365409255e-04]], |
| 720 | + [[9.9999439716339111e-01, -3.2541253603994846e-03, -8.0110825365409255e-04], |
| 721 | + [9.9998968839645386e-01, -3.1763643492013216e-03, -3.2474612817168236e-03]], |
| 722 | + [[9.9998968839645386e-01, -3.1763643492013216e-03, -3.2474612817168236e-03], |
| 723 | + [9.9998861551284790e-01, -8.2993711112067103e-04, -4.7004125081002712e-03]], |
| 724 | + [[9.9998861551284790e-01, -8.2993711112067103e-04, -4.7004125081002712e-03], |
| 725 | + [9.9999368190765381e-01, 1.7522916896268725e-03, -3.0944822356104851e-03]], |
| 726 | + [[9.9999368190765381e-01, 1.7522916896268725e-03, -3.0944822356104851e-03], |
| 727 | + [9.9999833106994629e-01, 1.6786820488050580e-03, -6.4892979571595788e-04]], |
| 728 | + [[9.9999833106994629e-01, 1.6786820488050580e-03, -6.4892979571595788e-04], |
| 729 | + [9.9999946355819702e-01, -6.7040475551038980e-04, 8.0396590055897832e-04]] |
| 730 | + ]) |
| 731 | + |
| 732 | + face_edges_lonlat = np.array( |
| 733 | + [[_xyz_to_lonlat_rad(*edge[0]), _xyz_to_lonlat_rad(*edge[1])] for edge in face_edges_cart]) |
| 734 | + |
| 735 | + bounds = _populate_face_latlon_bound(face_edges_cart, face_edges_lonlat) |
| 736 | + |
| 737 | + # The expected bounds should not contains the south pole [0,-0.5*np.pi] |
| 738 | + self.assertTrue(bounds[1][0] != 0.0) |
| 739 | + |
| 740 | + |
| 741 | + |
| 742 | + |
624 | 743 | def test_populate_bounds_node_on_pole(self): |
625 | 744 | # Generate a normal face that is crossing the antimeridian |
626 | 745 | vertices_lonlat = [[10.0, 90.0], [10.0, 10.0], [50.0, 10.0], [50.0, 60.0]] |
@@ -1116,6 +1235,8 @@ def test_populate_bounds_normal(self): |
1116 | 1235 | is_GCA_list=[True, False, True, False]) |
1117 | 1236 | nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
1118 | 1237 |
|
| 1238 | + |
| 1239 | + |
1119 | 1240 | def test_populate_bounds_antimeridian(self): |
1120 | 1241 | # Generate a normal face that is crossing the antimeridian |
1121 | 1242 | vertices_lonlat = [[350, 60.0], [350, 10.0], [50.0, 10.0], [50.0, 60.0]] |
@@ -1173,7 +1294,7 @@ def test_populate_bounds_node_on_pole(self): |
1173 | 1294 | nt.assert_allclose(bounds, expected_bounds, atol=ERROR_TOLERANCE) |
1174 | 1295 |
|
1175 | 1296 | def test_populate_bounds_edge_over_pole(self): |
1176 | | - # Generate a normal face that is crossing the antimeridian |
| 1297 | + # Generate a normal face that is around the north pole |
1177 | 1298 | vertices_lonlat = [[210.0, 80.0], [350.0, 60.0], [10.0, 60.0], [30.0, 80.0]] |
1178 | 1299 | vertices_lonlat = np.array(vertices_lonlat) |
1179 | 1300 |
|
@@ -1288,6 +1409,31 @@ def test_populate_bounds_GCAList_mix(self): |
1288 | 1409 | nt.assert_allclose(face_bounds[i], expected_bounds[i], atol=ERROR_TOLERANCE) |
1289 | 1410 |
|
1290 | 1411 |
|
| 1412 | +# Test class |
| 1413 | +class TestLatlonBoundsFiles: |
| 1414 | + |
| 1415 | + def test_face_bounds(self): |
| 1416 | + """Test to ensure ``Grid.face_bounds`` works correctly for all grid |
| 1417 | + files.""" |
| 1418 | + for grid_path in grid_files_latlonBound: |
| 1419 | + try: |
| 1420 | + # Open the grid file |
| 1421 | + self.uxgrid = ux.open_grid(grid_path) |
| 1422 | + |
| 1423 | + # Test: Ensure the bounds are obtained |
| 1424 | + bounds = self.uxgrid.bounds |
| 1425 | + assert bounds is not None, f"Grid.face_bounds should not be None for {grid_path}" |
| 1426 | + |
| 1427 | + except Exception as e: |
| 1428 | + # Print the failing grid file and re-raise the exception |
| 1429 | + print(f"Test failed for grid file: {grid_path}") |
| 1430 | + raise e |
| 1431 | + |
| 1432 | + finally: |
| 1433 | + # Clean up the grid object |
| 1434 | + del self.uxgrid |
| 1435 | + |
| 1436 | + |
1291 | 1437 | class TestGeoDataFrame(TestCase): |
1292 | 1438 |
|
1293 | 1439 | def test_to_gdf(self): |
|
0 commit comments