1313connectivity_arrays = ["cellsOnEdge" ,
1414 "cellsOnVertex" ,
1515 "verticesOnEdge" ,
16- "verticesOnCell" ]
16+ "verticesOnCell" ,
17+ "edgesOnVertex" ]
1718
1819
1920class Descriptor :
@@ -48,6 +49,7 @@ def __init__(self, ds, projection=None, transform=None, use_latlon=False):
4849 self .latlon = use_latlon
4950 self .projection = projection
5051 self .transform = transform
52+ self ._pre_projected = False
5153
5254 # if mesh is on a sphere, force the use of lat lon coords
5355 if ds .attrs ["on_a_sphere" ].strip ().upper () == 'YES' :
@@ -60,6 +62,7 @@ def __init__(self, ds, projection=None, transform=None, use_latlon=False):
6062 # reproject the minimal dataset, even for non-spherical meshes
6163 if projection and transform :
6264 self ._transform_coordinates (projection , transform )
65+ self ._pre_projected = True
6366
6467 def create_minimal_dataset (self , ds ):
6568 """
@@ -95,8 +98,8 @@ def fix_outofbounds_indices(ds, array_name):
9598 # list of coordinate / connectivity arrays needed to create patches
9699 mesh_arrays = coordinate_arrays + connectivity_arrays
97100
98- # get the subset of arrays from the mesh dataset
99- minimal_ds = ds [mesh_arrays ]
101+ # get subset of arrays from mesh and load into memory if dask arrays
102+ minimal_ds = ds [mesh_arrays ]. load ()
100103
101104 # delete the attributes in the minimal dataset to avoid confusion
102105 minimal_ds .attrs .clear ()
@@ -138,6 +141,17 @@ def vertex_patches(self):
138141 patches = self ._fix_antimeridian (patches , "Vertex" )
139142 return patches
140143
144+ def get_transform (self ):
145+ """
146+ """
147+
148+ if self ._pre_projected :
149+ transform = self .projection
150+ else :
151+ transform = self .transform
152+
153+ return transform
154+
141155 def _transform_coordinates (self , projection , transform ):
142156 """
143157 """
@@ -231,74 +245,128 @@ def transform_patches(self, patches, projection, transform):
231245
232246
233247def _compute_cell_patches (ds ):
248+ """Create cell patches (i.e. Primary cells) for an MPAS mesh.
234249
250+ All cell patches have `ds.sizes["maxEdges"]` nodes, even if `nEdgesOnCell`
251+ for the given cell is less than maxEdges. We choosed to deal with ragged
252+ indices (i.e. node indices greater than `nEdgesOnCell` for a given cell)
253+ by repeating the first node of the patch. Nodes are ordered counter
254+ clockwise aroudn the cell center.
255+ """
256+ # get the maximum number of edges on a cell
257+ maxEdges = ds .sizes ["maxEdges" ]
235258 # connectivity arrays have already been zero indexed
236259 verticesOnCell = ds .verticesOnCell
237260 # get a mask of the active vertices
238261 mask = verticesOnCell == - 1
239262
240- # get the coordinates needed to patch construction
241- xVertex = ds .xVertex
242- yVertex = ds .yVertex
263+ # tile the first vertices index
264+ firstVertex = np .tile (verticesOnCell [:, 0 ], (maxEdges , 1 )).T
265+ # set masked vertices to the first vertex of the cell
266+ verticesOnCell = np .where (mask , firstVertex , verticesOnCell )
243267
244268 # reshape/expand the vertices coordinate arrays
245- x_vert = np . ma . MaskedArray ( xVertex [verticesOnCell ], mask = mask )
246- y_vert = np . ma . MaskedArray ( yVertex [verticesOnCell ], mask = mask )
269+ x_nodes = ds . xVertex . values [verticesOnCell ]
270+ y_nodes = ds . yVertex . values [verticesOnCell ]
247271
248- verts = np .ma . stack ((x_vert , y_vert ), axis = - 1 )
272+ nodes = np .stack ((x_nodes , y_nodes ), axis = - 1 )
249273
250- return verts
274+ return nodes
251275
252276
253- def _compute_edge_patches (ds , latlon = False ):
277+ def _compute_edge_patches (ds ):
278+ """Create edge patches for an MPAS mesh.
279+
280+ Edge patches have four nodes which typically correspond to the two cell
281+ centers of the `cellsOnEdge` and the two vertices which make up the edge.
282+ For an edge patch along a culled mesh boundary one of the cell centers
283+ usually used to construct the patch will be missing, so the corresponding
284+ node will be collapsed to the edge coordinate.
285+ """
254286
255287 # connectivity arrays have already been zero indexed
256288 cellsOnEdge = ds .cellsOnEdge
257289 verticesOnEdge = ds .verticesOnEdge
258-
259- # is this masking correct ??
290+ # condition should only be true once per row or else wouldn't be an edge
260291 cellMask = cellsOnEdge < 0
261- vertexMask = verticesOnEdge < 0
262-
263- # get the coordinates needed to patch construction
264- xCell = ds .xCell
265- yCell = ds .yCell
266- xVertex = ds .xVertex
267- yVertex = ds .yVertex
268292
269293 # get subset of cell coordinate arrays corresponding to edge patches
270- xCell = np . ma . MaskedArray ( xCell [cellsOnEdge ], mask = cellMask )
271- yCell = np . ma . MaskedArray ( yCell [cellsOnEdge ], mask = cellMask )
294+ xCell = ds . xCell . values [cellsOnEdge ]
295+ yCell = ds . yCell . values [cellsOnEdge ]
272296 # get subset of vertex coordinate arrays corresponding to edge patches
273- xVertex = np . ma . MaskedArray ( xVertex [verticesOnEdge ], mask = vertexMask )
274- yVertex = np . ma . MaskedArray ( yVertex [verticesOnEdge ], mask = vertexMask )
297+ xVertex = ds . xVertex . values [verticesOnEdge ]
298+ yVertex = ds . yVertex . values [verticesOnEdge ]
275299
276- x_vert = np .ma .stack ((xCell [:, 0 ], xVertex [:, 0 ],
277- xCell [:, 1 ], xVertex [:, 1 ]), axis = - 1 )
300+ # if only one cell on edge (i.e. along a culled boundary), then collapse
301+ # the node corresponding to the missing cell back the edge location
302+ if np .any (cellMask ):
303+ xCell = np .where (cellMask , ds .xEdge .values [:, np .newaxis ], xCell )
304+ yCell = np .where (cellMask , ds .yEdge .values [:, np .newaxis ], yCell )
278305
279- y_vert = np .ma . stack ((yCell [:, 0 ], yVertex [:, 0 ],
280- yCell [:, 1 ], yVertex [:, 1 ]), axis = - 1 )
306+ x_nodes = np .stack ((xCell [:, 0 ], xVertex [:, 0 ],
307+ xCell [:, 1 ], xVertex [:, 1 ]), axis = - 1 )
281308
282- verts = np .ma .stack ((x_vert , y_vert ), axis = - 1 )
309+ y_nodes = np .stack ((yCell [:, 0 ], yVertex [:, 0 ],
310+ yCell [:, 1 ], yVertex [:, 1 ]), axis = - 1 )
283311
284- return verts
312+ nodes = np . stack (( x_nodes , y_nodes ), axis = - 1 )
285313
314+ return nodes
286315
287- def _compute_vertex_patches (ds , latlon = False ):
288316
289- # connectivity arrays have already been zero indexed
290- cellsOnVertex = ds .cellsOnVertex
291- # get a mask of the active vertices
292- mask = ds .cellsOnVertex == - 1
317+ def _compute_vertex_patches (ds ):
318+ """Create vertex patches (i.e. Dual Cells) for an MPAS mesh.
293319
294- # get the coordinates needed to patch construction
295- xCell = ds .xCell
296- yCell = ds .yCell
320+ Vertex patches have 6 nodes despite the typical dual cell only having
321+ three nodes (i.e. the cell centers of three cells on the vertex) in order
322+ ease the creation of vertex patches along culled mesh boundaries.
323+ The typical vertex patch will be comprised of the edges and cell centers
324+ of the `cellsOnVertex`. As the MPAS Mesh Specs (version 1.0: Section 5.3)
325+ outlines: "Edges lead cells as they move around vertex". So, the first node
326+ in a vertex patch will correspond to an edge (if present).
297327
298- # reshape/expand the vertices coordinate arrays
299- x_vert = np .ma .MaskedArray (xCell [cellsOnVertex ], mask = mask )
300- y_vert = np .ma .MaskedArray (yCell [cellsOnVertex ], mask = mask )
328+ For patches along culled boundaries, if an edge and/or cell center is
329+ missing the corresponding node will be collapsed to the patches vertex
330+ position.
331+ """
332+ nVertices = ds .sizes ["nVertices" ]
333+ vertexDegree = ds .sizes ["vertexDegree" ]
301334
302- verts = np .ma .stack ((x_vert , y_vert ), axis = - 1 )
335+ nodes = np .zeros ((nVertices , vertexDegree * 2 , 2 ))
336+ # connectivity arrays have already been zero indexed
337+ cellsOnVertex = ds .cellsOnVertex .values
338+ edgesOnVertex = ds .edgesOnVertex .values
339+ # get a mask of active nodes
340+ cellMask = cellsOnVertex == - 1
341+ edgeMask = edgesOnVertex == - 1
342+ unionMask = cellMask & edgeMask
303343
304- return verts
344+ # get the coordinates needed to patch construction
345+ xCell = ds .xCell .values
346+ yCell = ds .yCell .values
347+ xEdge = ds .xEdge .values
348+ yEdge = ds .yEdge .values
349+ # convert vertex coordinates to column vectors for broadcasting below
350+ xVertex = ds .xVertex .values [:, np .newaxis ]
351+ yVertex = ds .yVertex .values [:, np .newaxis ]
352+
353+ # if edge is missing collapse edge node to vertex, else leave at edge
354+ nodes [:, ::2 , 0 ] = np .where (edgeMask , xVertex , xEdge [edgesOnVertex ])
355+ nodes [:, ::2 , 1 ] = np .where (edgeMask , yVertex , yEdge [edgesOnVertex ])
356+
357+ # if cell is missing collapse cell node to vertex, else leave at cell
358+ nodes [:, 1 ::2 , 0 ] = np .where (cellMask , xVertex , xCell [cellsOnVertex ])
359+ nodes [:, 1 ::2 , 1 ] = np .where (cellMask , yVertex , yCell [cellsOnVertex ])
360+
361+ # -------------------------------------------------------------------------
362+ # NOTE: While the condition below probably only applies to the final edge
363+ # node we apply it to all, since the conditions above ensure the
364+ # patches will still be created correctly
365+ # -------------------------------------------------------------------------
366+ # if cell and edge missing collapse edge node to the first edge.
367+ # Because edges lead the vertices this ensures the patch encompasses
368+ # the full kite area and is propely closed.
369+ nodes [:, ::2 , 0 ] = np .where (unionMask , nodes [:, 0 :1 , 0 ], nodes [:, ::2 , 0 ])
370+ nodes [:, ::2 , 1 ] = np .where (unionMask , nodes [:, 0 :1 , 1 ], nodes [:, ::2 , 1 ])
371+
372+ return nodes
0 commit comments