Skip to content

Commit 75eb688

Browse files
committed
Add a tool for creating transect masks including distance
1 parent 6b528d3 commit 75eb688

File tree

2 files changed

+212
-0
lines changed

2 files changed

+212
-0
lines changed

ocean/transects/python/README.md

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Python Transect Tools
2+
3+
## compute_transects.py
4+
5+
Computes transport through sections.
6+
7+
Example call:
8+
```
9+
./compute_transects.py
10+
-k transect_masks.nc
11+
-m MPAS_mesh.nc
12+
-t 'RUN_PATH/analysis_members/timeSeriesStatsMonthly.*.nc'
13+
-n 'all'
14+
```
15+
To create the `transect_masks.nc` file, load e3sm-unified and:
16+
```
17+
MpasMaskCreator.x MPAS_mesh.nc transect_masks.nc -f transect_definitions.geojson
18+
```
19+
where the `transect_definitions.geojson` file includes a sequence of lat/lon
20+
points for each transect.
21+
22+
On LANL IC, example file is at
23+
```
24+
/usr/projects/climate/mpeterse/analysis_input_files/geojson_files/SingleRegionAtlanticWTransportTransects.geojson
25+
```
26+
27+
## create_transect_masks.py
28+
29+
Requires a conda environment with:
30+
* `python`
31+
* `geometric_features`
32+
* `matplotlib`
33+
* `mpas_tools`
34+
* `netcdf4`
35+
* `numpy`
36+
* `scipy`
37+
* `shapely`
38+
* `xarray`
39+
40+
The tools creates cell and edge masks, distance along the transect of cells
41+
and edges in the mask, and the edge sign on edges. It also includes
42+
information (distance, cell and edge indices, interpolation weights, etc.)
43+
along the transect itself to aid plotting.
44+
45+
The required inputs are an MPAS mesh file and a geojson file or the name of an
46+
ocean transect from `geometric_features`. The required output is a filename
47+
with the masks and other information about the transect.
48+
49+
50+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
5+
import numpy as np
6+
import xarray as xr
7+
from geometric_features import read_feature_collection
8+
from geometric_features import GeometricFeatures
9+
from mpas_tools.cime.constants import constants
10+
from mpas_tools.logging import LoggingContext
11+
from mpas_tools.io import write_netcdf
12+
from mpas_tools.mesh.mask import compute_mpas_transect_masks
13+
from mpas_tools.parallel import create_pool
14+
15+
from transect.vert import compute_transect
16+
17+
18+
def combine_transect_datasets(ds_mesh, fc_transect, out_filename, pool,
19+
logger):
20+
"""
21+
Combine transects masks on cells and edges with a dataset for plotting
22+
that describes how the transect slices through cell and edge geometry.
23+
Add fields on edges and cells that define the (mean) distance along the
24+
transect for each cell or edge in the transect
25+
"""
26+
27+
earth_radius = constants['SHR_CONST_REARTH']
28+
29+
ds_mask = compute_mpas_transect_masks(dsMesh=ds_mesh, fcMask=fc_transect,
30+
earthRadius=earth_radius,
31+
maskTypes=('cell', 'edge',),
32+
logger=logger,
33+
pool=pool, addEdgeSign=True)
34+
35+
feature = fc_transect.features[0]
36+
geom_type = feature['geometry']['type']
37+
if geom_type == 'LineString':
38+
coordinates = [feature['geometry']['coordinates']]
39+
elif geom_type == 'MultiLineString':
40+
coordinates = feature['geometry']['coordinates']
41+
else:
42+
raise ValueError(
43+
f'Unexpected geometry type for the transect {geom_type}')
44+
45+
lon = []
46+
lat = []
47+
for coords in coordinates:
48+
lon_local, lat_local = zip(*coords)
49+
lon.extend(lon_local)
50+
lat.extend(lat_local)
51+
lon = xr.DataArray(data=lon, dims='nTransectPoints')
52+
lat = xr.DataArray(data=lat, dims='nTransectPoints')
53+
54+
layer_thickness = ds_mesh.layerThickness
55+
bottom_depth = ds_mesh.bottomDepth
56+
min_level_cell = ds_mesh.minLevelCell
57+
max_level_cell = ds_mesh.maxLevelCell
58+
59+
ds_transect = compute_transect(lon, lat, ds_mesh, layer_thickness,
60+
bottom_depth, min_level_cell,
61+
max_level_cell, spherical=True)
62+
63+
ds = ds_mask
64+
for var in ds_transect.data_vars:
65+
ds[var] = ds_transect[var]
66+
67+
add_distance_field(ds, logger)
68+
69+
write_netcdf(ds, out_filename)
70+
71+
72+
def add_distance_field(ds, logger):
73+
"""
74+
Add fields on edges and cells that define the (mean) distance along the
75+
transect for each cell or edge in the transect
76+
"""
77+
78+
dist_cell = np.zeros(ds.sizes['nCells'])
79+
count_cell = np.zeros(ds.sizes['nCells'], dtype=int)
80+
dist_edge = np.zeros(ds.sizes['nEdges'])
81+
count_edge = np.zeros(ds.sizes['nEdges'], dtype=int)
82+
83+
logger.info('Adding transect distance fields on cells and edges...')
84+
85+
for segment in range(ds.sizes['nSegments']):
86+
icell = ds.horizCellIndices.isel(nSegments=segment).values
87+
iedge = ds.horizEdgeIndices.isel(nSegments=segment).values
88+
# the distance for the midpoint of the segment is the mean
89+
# of the distances of the end points
90+
dist = 0.5 * (ds.dNode.isel(nHorizNodes=segment) +
91+
ds.dNode.isel(nHorizNodes=segment + 1))
92+
dist_cell[icell] += dist
93+
count_cell[icell] += 1
94+
dist_edge[iedge] += dist
95+
count_edge[iedge] += 1
96+
97+
mask = count_cell > 0
98+
dist_cell[mask] /= count_cell[mask]
99+
dist_cell[np.logical_not(mask)] = np.nan
100+
101+
mask = count_edge > 0
102+
dist_edge[mask] /= count_edge[mask]
103+
dist_edge[np.logical_not(mask)] = np.nan
104+
105+
ds['transectDistanceCell'] = ('nCells', dist_cell)
106+
ds['transectDistanceEdge'] = ('nEdges', dist_edge)
107+
logger.info('Done.')
108+
109+
110+
def main():
111+
112+
parser = argparse.ArgumentParser(description='''
113+
creates transect edge and cell masks along with edge sign and distance
114+
along the transect''')
115+
parser.add_argument('-m', dest='mesh_filename',
116+
help='MPAS-Ocean horizontal and vertical filename',
117+
required=True)
118+
parser.add_argument('-g', dest='geojson_filename',
119+
help='Geojson filename with transect', required=False)
120+
parser.add_argument('-f', dest='feature_name',
121+
help='Name of an ocean transect from '
122+
'geometric_features',
123+
required=False)
124+
parser.add_argument('-o', dest='out_filename',
125+
help='Edge transect filename', required=True)
126+
parser.add_argument(
127+
"--process_count", required=False, dest="process_count", type=int,
128+
help="The number of processes to use to compute masks. The "
129+
"default is to use all available cores")
130+
parser.add_argument(
131+
"--multiprocessing_method", dest="multiprocessing_method",
132+
default='forkserver',
133+
help="The multiprocessing method use for python mask creation "
134+
"('fork', 'spawn' or 'forkserver')")
135+
args = parser.parse_args()
136+
137+
if args.geojson_filename is None and args.feature_name is None:
138+
raise ValueError('Must supply either a geojson file or a transect '
139+
'name')
140+
141+
if args.geojson_filename is not None:
142+
fc_transect = read_feature_collection(args.geojson_filename)
143+
else:
144+
gf = GeometricFeatures()
145+
fc_transect = gf.read(componentName='ocean', objectType='transect',
146+
featureNames=[args.feature_name])
147+
148+
ds_mesh = xr.open_dataset(args.mesh_filename)
149+
if 'Time' in ds_mesh.dims:
150+
ds_mesh = ds_mesh.isel(Time=0)
151+
152+
pool = create_pool(process_count=args.process_count,
153+
method=args.multiprocessing_method)
154+
155+
with LoggingContext('create_edge_transect') as logger:
156+
157+
combine_transect_datasets(ds_mesh, fc_transect, args.out_filename,
158+
pool, logger)
159+
160+
161+
if __name__ == '__main__':
162+
main()

0 commit comments

Comments
 (0)