Skip to content

Commit bde3e5d

Browse files
committed
Change add_land_locked_cells_to_mask to use xarray
1 parent 061bc4b commit bde3e5d

File tree

2 files changed

+129
-166
lines changed

2 files changed

+129
-166
lines changed

conda_package/mpas_tools/ocean/coastline_alteration.py

Lines changed: 98 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
unicode_literals
33

44
import numpy
5-
from netCDF4 import Dataset
6-
import os
7-
import shutil
5+
import xarray
86

97

108
def 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

Comments
 (0)