33
44import xarray
55import 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
152303def _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
0 commit comments