Skip to content

Commit d2a3aa7

Browse files
Try adding face_edges_spherical and face_edges_cartesian as properties to grid
1 parent 3fc6358 commit d2a3aa7

3 files changed

Lines changed: 132 additions & 77 deletions

File tree

uxarray/core/zonal.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44

55
from uxarray.grid.integrate import _zonal_face_weights, _zonal_face_weights_robust
6-
from uxarray.grid.utils import _get_cartesian_face_edge_nodes
6+
from uxarray.grid.utils import (
7+
_get_cartesian_face_edge_nodes,
8+
_get_lonlat_rad_face_edge_nodes,
9+
)
710

811

912

@@ -19,23 +22,35 @@ def _compute_non_conservative_zonal_mean(uxda, latitudes, use_robust_weights=Fal
1922
# Create a NumPy array for storing results
2023
result = np.zeros(shape, dtype=uxda.dtype)
2124

22-
faces_edge_nodes_xyz = _get_cartesian_face_edge_nodes(
25+
26+
faces_edges_cartesian = _get_cartesian_face_edge_nodes(
2327
uxgrid.face_node_connectivity.values,
2428
uxgrid.n_face,
25-
uxgrid.n_max_face_nodes,
29+
uxgrid.n_max_face_edges,
2630
uxgrid.node_x.values,
2731
uxgrid.node_y.values,
2832
uxgrid.node_z.values,
2933
)
3034

31-
bounds = uxgrid.bounds.values
35+
36+
faces_edges_lonlat_rad = _get_lonlat_rad_face_edge_nodes(
37+
uxgrid.face_node_connectivity.values,
38+
uxgrid.n_face,
39+
uxgrid.n_max_face_edges,
40+
uxgrid.node_lon.values,
41+
uxgrid.node_lat.values,
42+
)
43+
44+
# Check if the bounds are available
45+
if "bounds" not in uxgrid._ds:
46+
uxgrid._populate_bounds()
3247

3348
for i, lat in enumerate(latitudes):
3449
face_indices = uxda.uxgrid.get_faces_at_constant_latitude(lat)
3550

3651
z = np.sin(np.deg2rad(lat))
3752

38-
faces_edge_nodes_xyz_candidate = faces_edge_nodes_xyz[face_indices, :, :, :]
53+
faces_edge_nodes_xyz_candidate = faces_edges_cartesian[face_indices, :, :, :]
3954

4055
n_nodes_per_face_candidate = n_nodes_per_face[face_indices]
4156

uxarray/grid/geometry.py

Lines changed: 96 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def _unique_points(points, tolerance=ERROR_TOLERANCE):
103103

104104
@njit(cache=True)
105105
def _pad_closed_face_nodes(
106-
face_node_connectivity, n_face, n_max_face_nodes, n_nodes_per_face
106+
face_node_connectivity, n_face, n_max_face_nodes, n_nodes_per_face
107107
):
108108
"""Pads a closed array of face nodes by inserting the first element at any
109109
point a fill value is encountered.
@@ -123,14 +123,14 @@ def _pad_closed_face_nodes(
123123

124124

125125
def _build_polygon_shells(
126-
node_lon,
127-
node_lat,
128-
face_node_connectivity,
129-
n_face,
130-
n_max_face_nodes,
131-
n_nodes_per_face,
132-
projection=None,
133-
central_longitude=0.0,
126+
node_lon,
127+
node_lat,
128+
face_node_connectivity,
129+
n_face,
130+
n_max_face_nodes,
131+
n_nodes_per_face,
132+
projection=None,
133+
central_longitude=0.0,
134134
):
135135
"""Builds an array of polygon shells, which can be used with Shapely to
136136
construct polygons."""
@@ -266,7 +266,7 @@ def _grid_to_polygon_geodataframe(grid, periodic_elements, projection, project,
266266

267267

268268
def _build_geodataframe_without_antimeridian(
269-
polygon_shells, projected_polygon_shells, antimeridian_face_indices, engine
269+
polygon_shells, projected_polygon_shells, antimeridian_face_indices, engine
270270
):
271271
"""Builds a ``spatialpandas.GeoDataFrame`` or
272272
``geopandas.GeoDataFrame``excluding any faces that cross the
@@ -295,10 +295,10 @@ def _build_geodataframe_without_antimeridian(
295295

296296

297297
def _build_geodataframe_with_antimeridian(
298-
polygon_shells,
299-
projected_polygon_shells,
300-
antimeridian_face_indices,
301-
engine,
298+
polygon_shells,
299+
projected_polygon_shells,
300+
antimeridian_face_indices,
301+
engine,
302302
):
303303
"""Builds a ``spatialpandas.GeoDataFrame`` or ``geopandas.GeoDataFrame``
304304
including any faces that cross the antimeridian."""
@@ -317,9 +317,9 @@ def _build_geodataframe_with_antimeridian(
317317

318318

319319
def _build_corrected_shapely_polygons(
320-
polygon_shells,
321-
projected_polygon_shells,
322-
antimeridian_face_indices,
320+
polygon_shells,
321+
projected_polygon_shells,
322+
antimeridian_face_indices,
323323
):
324324
if projected_polygon_shells is not None:
325325
# use projected shells if a projection is applied
@@ -438,7 +438,7 @@ def _build_corrected_polygon_shells(polygon_shells):
438438

439439

440440
def _grid_to_matplotlib_polycollection(
441-
grid, periodic_elements, projection=None, **kwargs
441+
grid, periodic_elements, projection=None, **kwargs
442442
):
443443
"""Constructs and returns a ``matplotlib.collections.PolyCollection``"""
444444

@@ -642,7 +642,7 @@ def _get_polygons(grid, periodic_elements, projection=None, apply_projection=Tru
642642

643643

644644
def _grid_to_matplotlib_linecollection(
645-
grid, periodic_elements, projection=None, **kwargs
645+
grid, periodic_elements, projection=None, **kwargs
646646
):
647647
"""Constructs and returns a ``matplotlib.collections.LineCollection``"""
648648

@@ -913,7 +913,7 @@ def _check_intersection(ref_edge_xyz, edges_xyz):
913913
for i in range(n_edges):
914914
edge_xyz = edges_xyz[i]
915915
if allclose(
916-
intersection_point, edge_xyz[0], atol=ERROR_TOLERANCE
916+
intersection_point, edge_xyz[0], atol=ERROR_TOLERANCE
917917
) or allclose(intersection_point, edge_xyz[1], atol=ERROR_TOLERANCE):
918918
return 0
919919

@@ -1140,10 +1140,10 @@ def insert_pt_in_latlonbox(old_box, new_pt, is_lon_periodic=True):
11401140

11411141
@njit(cache=True)
11421142
def _populate_face_latlon_bound(
1143-
face_edges_xyz,
1144-
face_edges_lonlat,
1145-
is_latlonface=False,
1146-
is_GCA_list=None,
1143+
face_edges_xyz,
1144+
face_edges_lonlat,
1145+
is_latlonface=False,
1146+
is_GCA_list=None,
11471147
):
11481148
# Check if face_edges contains pole points
11491149
has_north_pole = pole_point_inside_polygon(1, face_edges_xyz, face_edges_lonlat)
@@ -1198,9 +1198,9 @@ def _populate_face_latlon_bound(
11981198
# Check if the node matches the pole point or if the pole point is within the edge
11991199
max_abs_diff = np.max(np.abs(n1_cart - pole_point_xyz))
12001200
if max_abs_diff <= ERROR_TOLERANCE or point_within_gca(
1201-
pole_point_xyz,
1202-
n1_cart,
1203-
n2_cart,
1201+
pole_point_xyz,
1202+
n1_cart,
1203+
n2_cart,
12041204
):
12051205
is_center_pole = False
12061206
face_latlon_array = insert_pt_in_latlonbox(
@@ -1292,15 +1292,15 @@ def _populate_face_latlon_bound(
12921292

12931293
# Insert extreme latitude points into the latlonbox
12941294
if (
1295-
abs(node1_lat_rad - lat_max) > ERROR_TOLERANCE
1296-
and abs(node2_lat_rad - lat_max) > ERROR_TOLERANCE
1295+
abs(node1_lat_rad - lat_max) > ERROR_TOLERANCE
1296+
and abs(node2_lat_rad - lat_max) > ERROR_TOLERANCE
12971297
):
12981298
face_latlon_array = insert_pt_in_latlonbox(
12991299
face_latlon_array, np.array([lat_max, node1_lon_rad])
13001300
)
13011301
elif (
1302-
abs(node1_lat_rad - lat_min) > ERROR_TOLERANCE
1303-
and abs(node2_lat_rad - lat_min) > ERROR_TOLERANCE
1302+
abs(node1_lat_rad - lat_min) > ERROR_TOLERANCE
1303+
and abs(node2_lat_rad - lat_min) > ERROR_TOLERANCE
13041304
):
13051305
face_latlon_array = insert_pt_in_latlonbox(
13061306
face_latlon_array, np.array([lat_min, node1_lon_rad])
@@ -1315,23 +1315,23 @@ def _populate_face_latlon_bound(
13151315

13161316
@njit(cache=True, parallel=True)
13171317
def compute_temp_latlon_array(
1318-
face_node_connectivity,
1319-
faces_edges_cartesian,
1320-
faces_edges_lonlat_rad,
1321-
n_nodes_per_face,
1322-
is_latlonface,
1323-
is_face_GCA_list,
1324-
INT_FILL_VALUE,
1318+
face_node_connectivity,
1319+
faces_edges_cartesian,
1320+
faces_edges_lonlat_rad,
1321+
n_nodes_per_face,
1322+
is_latlonface,
1323+
is_face_GCA_list,
1324+
INT_FILL_VALUE,
13251325
):
13261326
n_face = face_node_connectivity.shape[0]
13271327
temp_latlon_array = np.full((n_face, 2, 2), INT_FILL_VALUE, dtype=np.float64)
13281328
for face_idx in prange(n_face):
13291329
cur_face_edges_cartesian = faces_edges_cartesian[
1330-
face_idx, 0 : n_nodes_per_face[face_idx]
1331-
]
1330+
face_idx, 0: n_nodes_per_face[face_idx]
1331+
]
13321332
cur_faces_edges_lonlat_rad = faces_edges_lonlat_rad[
1333-
face_idx, 0 : n_nodes_per_face[face_idx]
1334-
]
1333+
face_idx, 0: n_nodes_per_face[face_idx]
1334+
]
13351335
if is_face_GCA_list is not None:
13361336
is_GCA_list = is_face_GCA_list[face_idx]
13371337
else:
@@ -1347,7 +1347,12 @@ def compute_temp_latlon_array(
13471347

13481348

13491349
def _populate_bounds(
1350-
grid, is_latlonface: bool = False, is_face_GCA_list=None, return_array=False
1350+
grid,
1351+
faces_edges_cartesian: np.ndarray = None,
1352+
faces_edges_lonlat_rad: np.ndarray = None,
1353+
is_latlonface: bool = False,
1354+
is_face_GCA_list=None,
1355+
return_array=False
13511356
):
13521357
"""Populates the bounds of the grid based on the geometry of its faces,
13531358
taking into account special conditions such as faces crossing the
@@ -1357,6 +1362,21 @@ def _populate_bounds(
13571362
13581363
Parameters
13591364
----------
1365+
grid : object
1366+
The grid object whose internal representation will be updated with the computed
1367+
face bounds.
1368+
1369+
faces_edges_cartesian : np.ndarray, optional
1370+
An array of shape (n_face, n_max_face_edges, 2, 3) containing the Cartesian coordinates
1371+
of each face's edges. This array may include dummy values for grids with holes.
1372+
Default is None.
1373+
1374+
faces_edges_lonlat_rad : np.ndarray, optional
1375+
An array of shape (n_face, n_max_face_edges, 2, 2) containing the longitude and latitude
1376+
(in radians) coordinates of each face's edges. This array may include dummy values for
1377+
grids with holes.
1378+
Default is None.
1379+
13601380
is_latlonface : bool, optional
13611381
A global flag that indicates if faces are latlon faces. If True, all faces
13621382
are treated as latlon faces, meaning that all edges are either longitude or
@@ -1407,25 +1427,29 @@ def _populate_bounds(
14071427
"""
14081428

14091429
# Ensure grid's cartesian coordinates are normalized
1430+
# TODO: Is it possible to have a more flexible normalization? (Avoid duplicated normalizations)
14101431
grid.normalize_cartesian_coordinates()
14111432

14121433
# Prepare data for Numba functions
1413-
faces_edges_cartesian = _get_cartesian_face_edge_nodes(
1414-
grid.face_node_connectivity.values,
1415-
grid.n_face,
1416-
grid.n_max_face_edges,
1417-
grid.node_x.values,
1418-
grid.node_y.values,
1419-
grid.node_z.values,
1420-
)
1434+
# TODO: remove the duplicate call for this function in both zonal average and bounds
1435+
if faces_edges_cartesian is None:
1436+
faces_edges_cartesian = _get_cartesian_face_edge_nodes(
1437+
grid.face_node_connectivity.values,
1438+
grid.n_face,
1439+
grid.n_max_face_edges,
1440+
grid.node_x.values,
1441+
grid.node_y.values,
1442+
grid.node_z.values,
1443+
)
14211444

1422-
faces_edges_lonlat_rad = _get_lonlat_rad_face_edge_nodes(
1423-
grid.face_node_connectivity.values,
1424-
grid.n_face,
1425-
grid.n_max_face_edges,
1426-
grid.node_lon.values,
1427-
grid.node_lat.values,
1428-
)
1445+
if faces_edges_lonlat_rad is None:
1446+
faces_edges_lonlat_rad = _get_lonlat_rad_face_edge_nodes(
1447+
grid.face_node_connectivity.values,
1448+
grid.n_face,
1449+
grid.n_max_face_edges,
1450+
grid.node_lon.values,
1451+
grid.node_lat.values,
1452+
)
14291453

14301454
n_nodes_per_face = grid.n_nodes_per_face.values
14311455

@@ -1512,16 +1536,16 @@ def stereographic_projection(lon, lat, central_lon, central_lat):
15121536

15131537
# Calculate constant used for calculation
15141538
k = 2.0 / (
1515-
1.0
1516-
+ np.sin(central_lat) * np.sin(lat)
1517-
+ np.cos(central_lat) * np.cos(lat) * np.cos(lon - central_lon)
1539+
1.0
1540+
+ np.sin(central_lat) * np.sin(lat)
1541+
+ np.cos(central_lat) * np.cos(lat) * np.cos(lon - central_lon)
15181542
)
15191543

15201544
# Calculate the x and y coordinates
15211545
x = k * np.cos(lat) * np.sin(lon - central_lon)
15221546
y = k * (
1523-
np.cos(central_lat) * np.sin(lat)
1524-
- np.sin(central_lat) * np.cos(lat) * np.cos(lon - central_lon)
1547+
np.cos(central_lat) * np.sin(lat)
1548+
- np.sin(central_lat) * np.cos(lat) * np.cos(lon - central_lon)
15251549
)
15261550

15271551
return x, y
@@ -1557,7 +1581,7 @@ def inverse_stereographic_projection(x, y, central_lon, central_lat):
15571581
central_lat = np.deg2rad(central_lat)
15581582

15591583
# Calculate constants used for calculation
1560-
p = np.sqrt(x**2 + y**2)
1584+
p = np.sqrt(x ** 2 + y ** 2)
15611585

15621586
c = 2 * np.arctan(p / 2)
15631587

@@ -1576,9 +1600,9 @@ def inverse_stereographic_projection(x, y, central_lon, central_lat):
15761600

15771601
@njit(cache=True)
15781602
def point_in_face(
1579-
edges_xyz,
1580-
point_xyz,
1581-
inclusive=True,
1603+
edges_xyz,
1604+
point_xyz,
1605+
inclusive=True,
15821606
):
15831607
"""Determines if a point lies inside a face.
15841608
@@ -1628,9 +1652,9 @@ def point_in_face(
16281652
for ind in range(len(edges_xyz)):
16291653
# If the point lies on an edge, return True if inclusive
16301654
if point_within_gca(
1631-
point_xyz,
1632-
edges_xyz[ind][0],
1633-
edges_xyz[ind][1],
1655+
point_xyz,
1656+
edges_xyz[ind][0],
1657+
edges_xyz[ind][1],
16341658
):
16351659
if inclusive:
16361660
return True
@@ -1723,7 +1747,7 @@ def _populate_max_face_radius(self):
17231747

17241748
@njit(cache=True)
17251749
def calculate_max_face_radius(
1726-
face_node_connectivity, node_lats_rad, node_lons_rad, face_lats_rad, face_lons_rad
1750+
face_node_connectivity, node_lats_rad, node_lons_rad, face_lats_rad, face_lons_rad
17271751
):
17281752
"""Finds the max face radius in the mesh.
17291753
Parameters
@@ -1797,7 +1821,7 @@ def haversine_distance(lon_a, lat_a, lon_b, lat_b):
17971821

17981822
# Haversine formula
17991823
equation_in_sqrt = (np.sin(dlat / 2) ** 2) + np.cos(lat_a) * np.cos(lat_b) * (
1800-
np.sin(dlon / 2) ** 2
1824+
np.sin(dlon / 2) ** 2
18011825
)
18021826
distance = 2 * np.arcsin(np.sqrt(equation_in_sqrt))
18031827

0 commit comments

Comments
 (0)