Skip to content

Commit cd11e1f

Browse files
committed
Make MOC basin masks with southern transects
Update the script for extracting southern countours accordingly.
1 parent cd4893f commit cd11e1f

File tree

2 files changed

+244
-28
lines changed

2 files changed

+244
-28
lines changed

conda_package/mpas_tools/ocean/moc.py

Lines changed: 235 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,188 @@
33

44
import xarray
55
import numpy
6-
7-
8-
def add_moc_southern_boundary_transects(in_filename, mesh_filename,
9-
out_filename):
6+
import logging
7+
import sys
8+
import copy
9+
import shapely.ops
10+
import shapely.geometry
11+
from geometric_features import GeometricFeatures, FeatureCollection
12+
13+
import mpas_tools.conversion
14+
from mpas_tools.io import write_netcdf
15+
16+
17+
def make_moc_basins_and_transects(gf, mesh_filename,
18+
mask_and_transect_filename,
19+
geojson_filename=None,
20+
mask_filename=None,
21+
logger=None):
1022
"""
23+
Builds features defining the ocean basins and southern transects used in
24+
computing the meridional overturning circulation (MOC)
1125
Parameters
1226
----------
13-
in_filename : str
14-
A file containing MOC region masks
27+
gf : ``GeometricFeatures``
28+
An object that knows how to download and read geometric featuers
1529
1630
mesh_filename : str
1731
A file with MPAS mesh information
1832
19-
out_filename : str
33+
mask_and_transect_filename : str
2034
A file to write the MOC region masks and southern-boundary transects to
35+
36+
geojson_filename : str, optional
37+
A file to write MOC regions to
38+
39+
mask_filename : str, optional
40+
A file to write MOC region masks to
41+
42+
logger : ``logging.Logger``, optional
43+
A logger for the output if not stdout
44+
45+
Returns
46+
-------
47+
fc : ``FeatureCollection``
48+
The new feature collection
49+
"""
50+
# Authors
51+
# -------
52+
# Xylar Asay-Davis
53+
54+
fcMOC = build_moc_basins(gf, logger)
55+
56+
if geojson_filename is not None:
57+
fcMOC.to_geojson(geojson_filename)
58+
59+
dsMesh = xarray.open_dataset(mesh_filename)
60+
dsMasks = mpas_tools.conversion.mask(dsMesh=dsMesh, fcMask=fcMOC,
61+
logger=logger)
62+
63+
if mask_filename is not None:
64+
write_netcdf(dsMasks, mask_filename)
65+
66+
dsMasksAndTransects = add_moc_southern_boundary_transects(dsMasks, dsMesh,
67+
logger=logger)
68+
write_netcdf(dsMasksAndTransects, mask_and_transect_filename)
69+
70+
71+
def build_moc_basins(gf, logger=None):
2172
"""
22-
mocMask = xarray.open_dataset(in_filename)
23-
mesh = xarray.open_dataset(mesh_filename)
73+
Builds features defining the ocean basins used in computing the meridional
74+
overturning circulation (MOC)
75+
Parameters
76+
----------
77+
gf : ``GeometricFeatures``
78+
An object that knows how to download and read geometric featuers
79+
80+
logger : ``logging.Logger``, optional
81+
A logger for the output if not stdout
82+
83+
Returns
84+
-------
85+
fc : ``FeatureCollection``
86+
The new feature collection
87+
"""
88+
# Authors
89+
# -------
90+
# Xylar Asay-Davis
91+
92+
useStdout = logger is None
93+
if useStdout:
94+
logger = logging.getLogger()
95+
logger.addHandler(logging.StreamHandler(sys.stdout))
96+
logger.setLevel(logging.INFO)
97+
98+
MOCSubBasins = {'Atlantic': ['Atlantic', 'Mediterranean'],
99+
'IndoPacific': ['Pacific', 'Indian'],
100+
'Pacific': ['Pacific'],
101+
'Indian': ['Indian']}
102+
103+
MOCSouthernBoundary = {'Atlantic': '34S',
104+
'IndoPacific': '34S',
105+
'Pacific': '6S',
106+
'Indian': '6S'}
107+
108+
fc = FeatureCollection()
109+
fc.set_group_name(groupName='MOCBasinRegionsGroup')
110+
111+
# build MOC basins from regions with the appropriate tags
112+
for basinName in MOCSubBasins:
113+
logger.info('{} MOC'.format(basinName))
114+
115+
logger.info(' * merging features')
116+
tags = ['{}_Basin'.format(basin) for basin in
117+
MOCSubBasins[basinName]]
118+
119+
fcBasin = gf.read(componentName='ocean', objectType='region',
120+
tags=tags, allTags=False)
121+
122+
logger.info(' * combining features')
123+
fcBasin = fcBasin.combine(featureName='{}_MOC'.format(basinName))
24124

25-
southernBoundaryEdges, southernBounderyEdgeSigns, \
125+
logger.info(' * masking out features south of MOC region')
126+
maskName = 'MOC mask {}'.format(MOCSouthernBoundary[basinName])
127+
fcMask = gf.read(componentName='ocean', objectType='region',
128+
featureNames=[maskName])
129+
# mask out the region covered by the mask
130+
fcBasin = fcBasin.difference(fcMask)
131+
132+
# remove various small polygons that are not part of the main MOC
133+
# basin shapes. Most are tiny but one below Australia is about 20
134+
# deg^2, so make the threshold 100 deg^2 to be on the safe side.
135+
fcBasin = _remove_small_polygons(fcBasin, minArea=100., logger=logger)
136+
137+
# add this basin to the full feature collection
138+
fc.merge(fcBasin)
139+
140+
if useStdout:
141+
logger.handlers = []
142+
143+
return fc
144+
145+
146+
def add_moc_southern_boundary_transects(dsMask, dsMesh, logger=None):
147+
"""
148+
Parameters
149+
----------
150+
dsMask : ``xarray.Dataset``
151+
Region masks defining MOC basins
152+
153+
dsMesh : ``xarray.Dataset``, optional
154+
An MPAS mesh on which the masks should be created
155+
156+
logger : ``logging.Logger``, optional
157+
A logger for the output if not stdout
158+
159+
Returns
160+
-------
161+
dsMask : ``xarray.Dataset``
162+
Region masks defining MOC basins and the corresponding southern-boundary
163+
transects
164+
"""
165+
166+
useStdout = logger is None
167+
if useStdout:
168+
logger = logging.getLogger()
169+
logger.addHandler(logging.StreamHandler(sys.stdout))
170+
logger.setLevel(logging.INFO)
171+
172+
southernBoundaryEdges, southernBoiundaryEdgeSigns, \
26173
southernBoundaryVertices = \
27-
_extract_southern_boundary(mesh, mocMask, latBuffer=3.*numpy.pi/180.)
174+
_extract_southern_boundary(dsMesh, dsMask, latBuffer=3.*numpy.pi/180.,
175+
logger=logger)
28176

29-
_add_transects_to_moc(mesh, mocMask, southernBoundaryEdges,
30-
southernBounderyEdgeSigns,
177+
_add_transects_to_moc(dsMesh, dsMask, southernBoundaryEdges,
178+
southernBoiundaryEdgeSigns,
31179
southernBoundaryVertices)
32180

33-
mocMask.to_netcdf(out_filename)
181+
if useStdout:
182+
logger.handlers = []
34183

184+
return dsMask
35185

36-
def _extract_southern_boundary(mesh, mocMask, latBuffer):
186+
187+
def _extract_southern_boundary(mesh, mocMask, latBuffer, logger):
37188
# Extrcts the southern boundary of each region mask in mocMask. Mesh info
38189
# is taken from mesh. latBuffer is a number of radians above the southern-
39190
# most point that should be considered to definitely be in the southern
@@ -56,12 +207,12 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
56207
cellsOnEdge < nCells)
57208

58209
southernBoundaryEdges = []
59-
southernBounderyEdgeSigns = []
210+
southernBoiundaryEdgeSigns = []
60211
southernBoundaryVertices = []
61212

62213
for iRegion in range(nRegions):
63214
name = mocMask.regionNames[iRegion].values.astype('U')
64-
print(name)
215+
logger.info(name)
65216
cellMask = mocMask.variables['regionCellMasks'][:, iRegion].values
66217

67218
# land cells are outside not in the MOC region
@@ -70,7 +221,7 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
70221
cellsOnEdgeMask[cellsOnEdgeInRange] = \
71222
cellMask[cellsOnEdge[cellsOnEdgeInRange]] == 1
72223

73-
print(' computing edge sign...')
224+
logger.info(' computing edge sign...')
74225
edgeSign = numpy.zeros(nEdges)
75226
# positive sign if the first cell on edge is in the region
76227
mask = numpy.logical_and(cellsOnEdgeMask[:, 0],
@@ -82,13 +233,13 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
82233
edgeSign[mask] = 1.
83234
isMOCBoundaryEdge = edgeSign != 0.
84235
edgesMOCBoundary = numpy.arange(nEdges)[isMOCBoundaryEdge]
85-
print(' done.')
236+
logger.info(' done.')
86237

87238
startEdge = numpy.argmin(latEdge[isMOCBoundaryEdge])
88239
startEdge = edgesMOCBoundary[startEdge]
89240
minLat = latEdge[startEdge]
90241

91-
print(' getting edge sequence...')
242+
logger.info(' getting edge sequence...')
92243
# follow the boundary from this point to get a loop of edges
93244
# Note: it is possible but unlikely that the southern-most point is
94245
# not within bulk region of the MOC mask if the region is not a single
@@ -97,7 +248,7 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
97248
_get_edge_sequence_on_boundary(startEdge, edgeSign, edgesOnVertex,
98249
verticesOnEdge)
99250

100-
print(' done: {} edges in transect.'.format(len(edgeSequence)))
251+
logger.info(' done: {} edges in transect.'.format(len(edgeSequence)))
101252

102253
aboveSouthernBoundary = latEdge[edgeSequence] > minLat + latBuffer
103254

@@ -121,7 +272,7 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
121272
if len(startIndices) == 0:
122273
# the whole sequence is the southern boundary
123274
southernBoundaryEdges.append(edgeSequence)
124-
southernBounderyEdgeSigns.append(edgeSequenceSigns)
275+
southernBoiundaryEdgeSigns.append(edgeSequenceSigns)
125276
southernBoundaryVertices.append(vertexSequence)
126277
continue
127278

@@ -136,7 +287,7 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
136287
indices = numpy.mod(indices, len(edgeSequence))
137288

138289
southernBoundaryEdges.append(edgeSequence[indices])
139-
southernBounderyEdgeSigns.append(edgeSequenceSigns[indices])
290+
southernBoiundaryEdgeSigns.append(edgeSequenceSigns[indices])
140291

141292
# we want one extra vertex in the vertex sequence
142293
indices = numpy.arange(endIndices[longest],
@@ -145,12 +296,12 @@ def _extract_southern_boundary(mesh, mocMask, latBuffer):
145296

146297
southernBoundaryVertices.append(vertexSequence[indices])
147298

148-
return (southernBoundaryEdges, southernBounderyEdgeSigns,
299+
return (southernBoundaryEdges, southernBoiundaryEdgeSigns,
149300
southernBoundaryVertices)
150301

151302

152303
def _add_transects_to_moc(mesh, mocMask, southernBoundaryEdges,
153-
southernBounderyEdgeSigns, southernBoundaryVertices):
304+
southernBoiundaryEdgeSigns, southernBoundaryVertices):
154305
# Creates transect fields in mocMask from the edges, edge signs and
155306
# vertices defining the southern boundaries. Mesh info (nEdges and
156307
# nVertices) is taken from the mesh file.
@@ -182,7 +333,7 @@ def _add_transects_to_moc(mesh, mocMask, southernBoundaryEdges,
182333
transectEdgeMasks[southernBoundaryEdges[iTransect], iTransect] = 1
183334

184335
transectEdgeMaskSigns[southernBoundaryEdges[iTransect], iTransect] \
185-
= southernBounderyEdgeSigns[iTransect]
336+
= southernBoiundaryEdgeSigns[iTransect]
186337

187338
transectCount = len(southernBoundaryEdges[iTransect])
188339
transectEdgeGlobalIDs[iTransect, 0:transectCount] \
@@ -267,3 +418,61 @@ def _get_edge_sequence_on_boundary(startEdge, edgeSign, edgesOnVertex,
267418
vertexSequence = numpy.array(vertexSequence)
268419

269420
return edgeSequence, edgeSequenceSigns, vertexSequence
421+
422+
423+
def _remove_small_polygons(fc, minArea, logger):
424+
"""
425+
A helper function to remove small polygons from a feature collection
426+
Parameters
427+
----------
428+
fc : ``FeatureCollection``
429+
The feature collection to remove polygons from
430+
minArea : float
431+
The minimum area (in square degrees) below which polygons should be
432+
removed
433+
Returns
434+
-------
435+
fcOut : ``FeatureCollection``
436+
The new feature collection with small polygons removed
437+
"""
438+
# Authors
439+
# -------
440+
# Xylar Asay-Davis
441+
442+
fcOut = FeatureCollection()
443+
444+
removedCount = 0
445+
for feature in fc.features:
446+
geom = feature['geometry']
447+
add = False
448+
if geom['type'] not in ['Polygon', 'MultiPolygon']:
449+
# no area to check, so just add it
450+
fcOut.add_feature(copy.deepcopy(feature))
451+
else:
452+
featureShape = shapely.geometry.shape(geom)
453+
if featureShape.type == 'Polygon':
454+
if featureShape.area > minArea:
455+
add = True
456+
else:
457+
removedCount += 1
458+
else:
459+
# a MultiPolygon
460+
outPolygons = []
461+
for polygon in featureShape:
462+
if polygon.area > minArea:
463+
outPolygons.append(polygon)
464+
else:
465+
removedCount += 1
466+
if len(outPolygons) > 0:
467+
outShape = shapely.ops.cascaded_union(outPolygons)
468+
feature['geometry'] = shapely.geometry.mapping(outShape)
469+
add = True
470+
if add:
471+
fcOut.add_feature(copy.deepcopy(feature))
472+
else:
473+
logger.info("{} has been removed.".format(
474+
feature['properties']['name']))
475+
476+
logger.info(' * Removed {} small polygons'.format(removedCount))
477+
478+
return fcOut

ocean/moc_southern_boundary_extractor/moc_southern_boundary_extractor.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
from __future__ import absolute_import, division, print_function, \
1616
unicode_literals
1717

18+
import xarray
1819
import argparse
1920

21+
from mpas_tools.io import \
22+
write_netcdf
2023
from mpas_tools.ocean.moc import \
2124
add_moc_southern_boundary_transects
2225

@@ -38,5 +41,9 @@
3841
required=True)
3942
args = parser.parse_args()
4043

41-
add_moc_southern_boundary_transects(args.in_file, args.mesh_file,
42-
args.out_file)
44+
dsMasks = xarray.open_dataset(args.in_file)
45+
dsMesh = xarray.open_dataset(args.mesh_file)
46+
47+
dsMasksAndTransects = add_moc_southern_boundary_transects(dsMasks, dsMesh)
48+
49+
write_netcdf(dsMasksAndTransects, args.out_file)

0 commit comments

Comments
 (0)