22 unicode_literals
33
44import numpy
5- from netCDF4 import Dataset
6- import os
7- import shutil
5+ import xarray
86
97
108def add_critical_land_blockages (dsMask , dsBlockages ):
@@ -72,100 +70,88 @@ def widen_transect_edge_masks(dsMask, dsMesh, latitude_threshold=43.0):
7270 return dsMask
7371
7472
75- def add_land_locked_cells_to_mask (input_mask_filename , output_mask_filename ,
76- mesh_filename , latitude_threshold = 43.0 ,
73+ def add_land_locked_cells_to_mask (dsMask , dsMesh , latitude_threshold = 43.0 ,
7774 nSweeps = 10 ):
7875 '''
7976 Find ocean cells that are land-locked, and alter the cell mask so that they
8077 are counted as land cells.
8178
8279 Parameters
8380 ----------
84- input_mask_filename : str
85- Mask file that includes cell and edge masks.
81+ dsMask : ``xarray.Dataset``
82+ A land-mask data set
8683
87- output_mask_filename : str
88- Mask file that includes cell and edge masks.
89-
90- mesh_filename : str
91- MPAS Mesh filename.
84+ dsMesh : ``xarray.Dataset``
85+ MPAS Mesh data set
9286
9387 latitude_threshold : float, optional
94- Minimum latitude, in degrees, for transect widening.
88+ Minimum latitude, in degrees, for transect widening
9589
9690 nSweeps : int, optional
97- Maximum number of sweeps to search for land-locked cells.
91+ Maximum number of sweeps to search for land-locked cells
92+
93+ Returns
94+ -------
95+ dsMask : ``xarray.Dataset``
96+ A copy of the land-mask data set with land-locked cells added to the
97+ mask for the first region
9898 '''
9999
100- # Obtain mesh variables
101- meshFile = Dataset (mesh_filename , "r" )
102- nCells = len (meshFile .dimensions ["nCells" ])
103- cellsOnCell = meshFile .variables ["cellsOnCell" ][:, :] - 1
104- nEdgesOnCell = meshFile .variables ["nEdgesOnCell" ][:]
105- latCell = numpy .rad2deg (meshFile .variables ["latCell" ][:])
106- lonCell = numpy .rad2deg (meshFile .variables ["lonCell" ][:])
107- meshFile .close ()
100+ dsMask = xarray .Dataset (dsMask )
101+ dsMesh = dsMesh .copy (deep = True )
102+
103+ landMask = dsMask .regionCellMasks .max (dim = 'nRegions' ) > 0
108104
109- _remove_file (output_mask_filename )
110- shutil .copyfile (input_mask_filename , output_mask_filename )
105+ dsMask ['landMaskDiagnostic' ] = xarray .where (landMask , 1 , 0 )
111106
112- # Obtain original cell mask from input file
113- inputMaskFile = Dataset (input_mask_filename , "r" )
114- regionCellMasks = inputMaskFile .variables ["regionCellMasks" ][:, :]
115- # set landMask = flattened regionCellMasks
116- landMask = numpy .amax (regionCellMasks , axis = 1 )
117- inputMaskFile .close ()
107+ print ("Running add_land_locked_cells_to_mask.py. Total number of cells: "
108+ "{}" .format (dsMesh .sizes ['nCells' ]))
118109
119- # Open output file
120- outputMaskFile = Dataset (output_mask_filename , "a" )
121- landMaskDiagnostic = outputMaskFile .createVariable (
122- "landMaskDiagnostic" , "i" , dimensions = ("nCells" ))
110+ cellsOnCell = dsMesh .cellsOnCell - 1
111+ nEdgesOnCell = dsMesh .nEdgesOnCell
123112
124- regionCellMasks = outputMaskFile ['regionCellMasks' ]
113+ nextCellsOnCell = cellsOnCell .copy (deep = True )
114+ prevCellsOnCell = cellsOnCell .copy (deep = True )
115+ for iEdgeOnCell in range (nextCellsOnCell .shape [1 ]):
116+ iP1 = numpy .mod (iEdgeOnCell + 1 , nEdgesOnCell )
117+ nextCellsOnCell [:, iEdgeOnCell ] = cellsOnCell [:, iP1 ]
118+ iM1 = numpy .mod (iEdgeOnCell - 1 , nEdgesOnCell )
119+ prevCellsOnCell [:, iEdgeOnCell ] = cellsOnCell [:, iM1 ]
125120
126- print ("Running add_land_locked_cells_to_mask.py. Total number of cells: "
127- "{}" .format (nCells ))
121+ dsMesh ['cellsOnCell' ] = cellsOnCell
122+ dsMesh ['nextCellsOnCell' ] = nextCellsOnCell
123+ dsMesh ['prevCellsOnCell' ] = prevCellsOnCell
124+ dsMesh ['latCell' ] = numpy .rad2deg (dsMesh .latCell )
125+ dsMesh ['lonCell' ] = numpy .rad2deg (dsMesh .lonCell )
128126
129127 landMask , removable = _remove_cells_with_isolated_edges1 (
130- landMask , landMaskDiagnostic , regionCellMasks , latCell , nEdgesOnCell ,
131- cellsOnCell , nCells , latitude_threshold )
128+ dsMask , dsMesh , landMask , latitude_threshold )
132129 landMask = _remove_cells_with_isolated_edges2 (
133- landMask , landMaskDiagnostic , regionCellMasks , removable ,
134- nEdgesOnCell , cellsOnCell , nCells , nSweeps )
135- oceanMask = _flood_fill (landMask , landMaskDiagnostic , removable , lonCell ,
136- latCell , nEdgesOnCell , cellsOnCell , nCells )
130+ dsMask , dsMesh , landMask , removable , nSweeps )
131+ oceanMask = _flood_fill (dsMask , dsMesh , landMask , removable )
137132 landMask = _revert_cells_with_connected_edges (
138- oceanMask , landMask , landMaskDiagnostic , regionCellMasks , removable ,
139- nEdgesOnCell , cellsOnCell , nCells , nSweeps )
140- outputMaskFile . close ()
133+ dsMask , dsMesh , oceanMask , landMask , removable , nSweeps )
134+
135+ return dsMask
141136
142137
143- def _remove_cells_with_isolated_edges1 (landMask , landMaskDiagnostic ,
144- regionCellMasks , latCell , nEdgesOnCell ,
145- cellsOnCell , nCells ,
138+ def _remove_cells_with_isolated_edges1 (dsMask , dsMesh , landMask ,
146139 latitude_threshold ):
147140 print ("Step 1: Searching for land-locked cells. Remove cells that only "
148141 "have isolated active edges." )
149142
150- landMaskNew = numpy .array (landMask )
151-
152- landMaskDiagnostic [:] = landMask
153-
154- removable = numpy .logical_and (numpy .abs (latCell ) >= latitude_threshold ,
155- landMask == 0 )
156-
157- nextCellsOnCell = numpy .zeros (cellsOnCell .shape , int )
158-
159- for iEdgeOnCell in range (nextCellsOnCell .shape [1 ]):
160- iP1 = numpy .mod (iEdgeOnCell + 1 , nEdgesOnCell )
161- nextCellsOnCell [:, iEdgeOnCell ] = \
162- cellsOnCell [numpy .arange (nCells ), iP1 ]
163-
164- valid = numpy .logical_and (removable .reshape (nCells , 1 ),
165- cellsOnCell >= 0 )
143+ landMaskNew = landMask .copy (deep = True )
166144
167145 active = numpy .logical_not (landMask )
146+ removable = numpy .logical_and (
147+ numpy .abs (dsMesh .latCell ) >= latitude_threshold , active )
148+
149+ cellsOnCell = dsMesh .cellsOnCell
150+ valid = numpy .logical_and (removable , cellsOnCell >= 0 )
168151 activeEdge = numpy .logical_and (valid , active [cellsOnCell ])
152+
153+ nextCellsOnCell = dsMesh .nextCellsOnCell
154+ valid = numpy .logical_and (removable , nextCellsOnCell >= 0 )
169155 activeNextEdge = numpy .logical_and (valid , active [nextCellsOnCell ])
170156
171157 # which vertices have adjacent active edges on this cell?
@@ -178,50 +164,37 @@ def _remove_cells_with_isolated_edges1(landMask, landMaskDiagnostic,
178164 landMaskNew [noActiveAdjacentEdges ] = 1
179165 landLockedCounter = numpy .count_nonzero (noActiveAdjacentEdges )
180166
181- regionCellMasks [:, 0 ] = numpy .maximum (regionCellMasks [:, 0 ],
182- noActiveAdjacentEdges )
167+ dsMask . regionCellMasks [:, 0 ] = numpy .maximum (dsMask . regionCellMasks [:, 0 ],
168+ 1 * noActiveAdjacentEdges )
183169
184- landMaskDiagnostic [noActiveAdjacentEdges ] = 2
170+ dsMask . landMaskDiagnostic [noActiveAdjacentEdges ] = 2
185171
186172 print (" Number of landLocked cells: {}" .format (landLockedCounter ))
187173
188174 return landMaskNew , removable
189175
190176
191- def _remove_cells_with_isolated_edges2 (landMask , landMaskDiagnostic ,
192- regionCellMasks , removable ,
193- nEdgesOnCell , cellsOnCell , nCells ,
177+ def _remove_cells_with_isolated_edges2 (dsMask , dsMesh , landMask , removable ,
194178 nSweeps ):
195179 print ("Step 2: Searching for land-locked cells. Remove cells that have "
196180 "any isolated active edges." )
197181
198- nextCellsOnCell = numpy .zeros (cellsOnCell .shape , int )
199- prevCellsOnCell = numpy .zeros (cellsOnCell .shape , int )
200-
201- for iEdgeOnCell in range (nextCellsOnCell .shape [1 ]):
202- iP1 = numpy .mod (iEdgeOnCell + 1 , nEdgesOnCell )
203- nextCellsOnCell [:, iEdgeOnCell ] = \
204- cellsOnCell [numpy .arange (nCells ), iP1 ]
205- iM1 = numpy .mod (iEdgeOnCell - 1 , nEdgesOnCell )
206- prevCellsOnCell [:, iEdgeOnCell ] = \
207- cellsOnCell [numpy .arange (nCells ), iM1 ]
182+ cellsOnCell = dsMesh .cellsOnCell
183+ nextCellsOnCell = dsMesh .nextCellsOnCell
184+ prevCellsOnCell = dsMesh .prevCellsOnCell
208185
209186 for iSweep in range (nSweeps ):
210187 landLockedCounter = 0
211- landMaskNew = numpy .array (landMask )
212-
213- mask = numpy .logical_and (removable ,
214- landMask == 0 )
188+ landMaskNew = landMask .copy (deep = True )
215189
216190 active = numpy .logical_not (landMask )
217- valid = numpy .logical_and (mask .reshape (nCells , 1 ),
218- cellsOnCell >= 0 )
191+ mask = numpy .logical_and (removable , active )
192+
193+ valid = numpy .logical_and (mask , cellsOnCell >= 0 )
219194 activeEdge = numpy .logical_and (valid , active [cellsOnCell ])
220- valid = numpy .logical_and (mask .reshape (nCells , 1 ),
221- nextCellsOnCell >= 0 )
195+ valid = numpy .logical_and (mask , nextCellsOnCell >= 0 )
222196 activeNextEdge = numpy .logical_and (valid , active [nextCellsOnCell ])
223- valid = numpy .logical_and (mask .reshape (nCells , 1 ),
224- prevCellsOnCell >= 0 )
197+ valid = numpy .logical_and (mask , prevCellsOnCell >= 0 )
225198 activePrevEdge = numpy .logical_and (valid , active [prevCellsOnCell ])
226199
227200 # an edge is land-locked if it is active but neither neighbor is active
@@ -235,8 +208,8 @@ def _remove_cells_with_isolated_edges2(landMask, landMaskDiagnostic,
235208 landLockedCounter = numpy .count_nonzero (landLockedCells )
236209 if landLockedCounter > 0 :
237210 landMaskNew [landLockedCells ] = 1
238- regionCellMasks [landLockedCells , 0 ] = 1
239- landMaskDiagnostic [landLockedCells ] = 3
211+ dsMask . regionCellMasks [landLockedCells , 0 ] = 1
212+ dsMask . landMaskDiagnostic [landLockedCells ] = 3
240213
241214 landMask = landMaskNew
242215 print (" Sweep: {} Number of landLocked cells removed: {}" .format (
@@ -247,20 +220,21 @@ def _remove_cells_with_isolated_edges2(landMask, landMaskDiagnostic,
247220 return landMask
248221
249222
250- def _flood_fill (landMask , landMaskDiagnostic , removable , lonCell , latCell ,
251- nEdgesOnCell , cellsOnCell , nCells ):
223+ def _flood_fill (dsMask , dsMesh , landMask , removable ):
252224 print ("Step 3: Perform flood fill, starting from open ocean." )
253225
254226 # init flood fill to 0 for water, -1 for land, 1 for known open ocean
255- floodFill = - 1 * numpy .ones (nCells , dtype = "i" )
256- mask = numpy .logical_and (removable , landMask == 0 )
257- floodFill [mask ] = 0
227+ floodFill = xarray .where (
228+ numpy .logical_and (removable , numpy .logical_not (landMask )), 0 , - 1 )
258229
259- openOceanMask = numpy .zeros (nCells , bool )
230+ latCell = dsMesh .latCell
231+ lonCell = dsMesh .lonCell
232+
233+ cellsOnCell = dsMesh .cellsOnCell
260234
261235 # North Pole
262236 mask = latCell > 84.0
263- openOceanMask = numpy . logical_or ( openOceanMask , mask )
237+ openOceanMask = mask
264238
265239 # Arctic
266240 mask = numpy .logical_and (
@@ -312,20 +286,22 @@ def _flood_fill(landMask, landMaskDiagnostic, removable, lonCell, latCell,
312286 nFloodableCells = numpy .count_nonzero (floodFill == 0 )
313287 print (" Initial number of flood cells: {}" .format (nFloodableCells ))
314288
315- landMaskDiagnostic [floodFill == 1 ] = 5
289+ dsMask . landMaskDiagnostic [floodFill == 1 ] = 5
316290
317291 # sweep over neighbors of known open ocean points
318- for iSweep in range (0 , nCells ):
292+ for iSweep in range (dsMesh . sizes [ ' nCells' ] ):
319293
320294 newFloodCellsThisSweep = 0
321295 mask = floodFill == 0
296+ cellIndices = numpy .nonzero (mask .values )[0 ]
322297 for iCellOnCell in range (cellsOnCell .shape [1 ]):
323- neighbors = cellsOnCell [:, iCellOnCell ]
324- fill = numpy .logical_and (
325- mask ,
326- numpy .logical_and (neighbors >= 0 , floodFill [neighbors ] == 1 ))
327- floodFill [fill ] = 1
328- newFloodCellsThisSweep += numpy .count_nonzero (fill )
298+ neighbors = cellsOnCell [cellIndices , iCellOnCell ]
299+ filledNeighbors = numpy .logical_and (neighbors >= 0 ,
300+ floodFill [neighbors ] == 1 )
301+ fillIndices = cellIndices [filledNeighbors .values ]
302+ if (len (fillIndices ) > 0 ):
303+ floodFill [fillIndices ] = 1
304+ newFloodCellsThisSweep += len (fillIndices )
329305
330306 print (" Sweep {} new flood cells this sweep: {}" .format (
331307 iSweep , newFloodCellsThisSweep ))
@@ -340,42 +316,29 @@ def _flood_fill(landMask, landMaskDiagnostic, removable, lonCell, latCell,
340316 return oceanMask
341317
342318
343- def _revert_cells_with_connected_edges (oceanMask , landMask , landMaskDiagnostic ,
344- regionCellMasks , removable ,
345- nEdgesOnCell , cellsOnCell , nCells ,
346- nSweeps ):
319+ def _revert_cells_with_connected_edges (dsMask , dsMesh , oceanMask , landMask ,
320+ removable , nSweeps ):
347321 print ("Step 4: Searching for land-locked cells, step 3: revert cells with "
348322 "connected active edges" )
349323
350- nextCellsOnCell = numpy .zeros (cellsOnCell .shape , int )
351- prevCellsOnCell = numpy .zeros (cellsOnCell .shape , int )
352-
353- for iEdgeOnCell in range (nextCellsOnCell .shape [1 ]):
354- iP1 = numpy .mod (iEdgeOnCell + 1 , nEdgesOnCell )
355- nextCellsOnCell [:, iEdgeOnCell ] = \
356- cellsOnCell [numpy .arange (nCells ), iP1 ]
357- iM1 = numpy .mod (iEdgeOnCell - 1 , nEdgesOnCell )
358- prevCellsOnCell [:, iEdgeOnCell ] = \
359- cellsOnCell [numpy .arange (nCells ), iM1 ]
324+ cellsOnCell = dsMesh .cellsOnCell
325+ nextCellsOnCell = dsMesh .nextCellsOnCell
326+ prevCellsOnCell = dsMesh .prevCellsOnCell
360327
361328 for iSweep in range (nSweeps ):
362329 landMaskNew = numpy .array (landMask )
363330
364331 # only remove a cell that was added in Step 2,
365332 # _remove_cells_with_isolated_edges2
366- mask = numpy .logical_and (removable , landMaskDiagnostic [:] == 3 )
333+ mask = numpy .logical_and (removable , dsMask . landMaskDiagnostic == 3 )
367334
368- valid = numpy .logical_and ( mask . reshape ( nCells , 1 ),
369- cellsOnCell >= 0 )
335+ notLand = numpy .logical_not ( landMask )
336+ valid = numpy . logical_and ( mask , cellsOnCell >= 0 )
370337 oceanEdge = numpy .logical_and (valid , oceanMask [cellsOnCell ])
371- valid = numpy .logical_and (mask .reshape (nCells , 1 ),
372- nextCellsOnCell >= 0 )
373- activeNextEdge = numpy .logical_and (valid ,
374- landMask [nextCellsOnCell ] == 0 )
375- valid = numpy .logical_and (mask .reshape (nCells , 1 ),
376- prevCellsOnCell >= 0 )
377- activePrevEdge = numpy .logical_and (valid ,
378- landMask [prevCellsOnCell ] == 0 )
338+ valid = numpy .logical_and (mask , nextCellsOnCell >= 0 )
339+ activeNextEdge = numpy .logical_and (valid , notLand [nextCellsOnCell ])
340+ valid = numpy .logical_and (mask , prevCellsOnCell >= 0 )
341+ activePrevEdge = numpy .logical_and (valid , notLand [prevCellsOnCell ])
379342
380343 reactivate = numpy .any (
381344 numpy .logical_and (
@@ -385,9 +348,9 @@ def _revert_cells_with_connected_edges(oceanMask, landMask, landMaskDiagnostic,
385348 landLockedCounter = numpy .count_nonzero (reactivate )
386349 if landLockedCounter > 0 :
387350 landMaskNew [reactivate ] = 0
388- regionCellMasks [reactivate , 0 ] = 0
351+ dsMask . regionCellMasks [reactivate , 0 ] = 0
389352 oceanMask [reactivate ] = 1
390- landMaskDiagnostic [reactivate ] = 4
353+ dsMask . landMaskDiagnostic [reactivate ] = 4
391354
392355 landMask = landMaskNew
393356 print (" Sweep: {} Number of land-locked cells returned: {}" .format (
@@ -396,10 +359,3 @@ def _revert_cells_with_connected_edges(oceanMask, landMask, landMaskDiagnostic,
396359 break
397360
398361 return landMask
399-
400-
401- def _remove_file (fileName ):
402- try :
403- os .remove (fileName )
404- except OSError :
405- pass
0 commit comments