Skip to content

Commit 29195e6

Browse files
authored
Merge pull request #675 from matthewhoffman/landice/convert-cellcoords-to-sphere
Add script to convert cell coordinates from plane to sphere
2 parents 811bbeb + a24eeea commit 29195e6

2 files changed

Lines changed: 136 additions & 0 deletions

File tree

conda_package/docs/landice/visualization.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,29 @@ used to add grounding lines to maps as countours.
119119
ax=ax,
120120
cmap="plasma", times=[0])
121121
plot_map(data_path + files[0], "bedTopography", ax=ax, time=0)
122+
123+
Converting Planar Coordinates for Spherical ParaView Rendering
124+
==============================================================
125+
126+
The script ``landice/mesh_tools_li/convert_cell_coordinates_to_sphere.py``
127+
can be used to create a copy of a MALI file with ``xCell``, ``yCell`` and
128+
``zCell`` replaced by spherical coordinates for visualization in ParaView.
129+
130+
The input file is never modified. By default, output is written to
131+
``<infile stem>_sphere<suffix>``.
132+
133+
.. code-block:: bash
134+
135+
python landice/mesh_tools_li/convert_cell_coordinates_to_sphere.py -i output_state_2015.nc
136+
137+
You can also choose an explicit output file and optionally warp the surface
138+
to create 3d ice-sheet surface topography on the sphere
139+
using ``upperSurface`` (or ``thickness`` + ``bedTopography`` if
140+
``upperSurface`` is not available).
141+
142+
.. code-block:: bash
143+
144+
python landice/mesh_tools_li/convert_cell_coordinates_to_sphere.py \
145+
-i output_state_2015.nc \
146+
-o output_state_2015_sphere.nc \
147+
-w -s 100
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
'''
3+
Script to allow plotting MALI output on a sphere in Paraview.
4+
This works by replacing x/y/zCell fields and modifying global attributes so
5+
Paraview will plot as a spherical mesh. Paraview only uses those 3 fields for
6+
plotting.
7+
The script writes to a separate output file and leaves the input file unchanged.
8+
Note that the modified coordinates will make the file no longer compatible
9+
with MALI or many postprocessing tools, so this is meant as a modification
10+
for visualization in Paraview only.
11+
'''
12+
13+
import argparse
14+
import shutil
15+
from pathlib import Path
16+
from netCDF4 import Dataset
17+
import numpy as np
18+
import sys
19+
20+
parser = argparse.ArgumentParser(
21+
prog='convert_cell_coordinates_to_sphere.py',
22+
description=__doc__,
23+
formatter_class=argparse.RawDescriptionHelpFormatter)
24+
parser.add_argument("-i", "--infile", dest="infile", default="output.nc",
25+
help="input file to convert to spherical mesh convention")
26+
parser.add_argument("-o", "--outfile", dest="outfile", default=None,
27+
help="output file to write (default: <infile stem>_sphere<suffix>)")
28+
parser.add_argument("-r", dest="radius", type=float, default=6371220.,
29+
help="sphere radius to use (m)")
30+
parser.add_argument("-w", dest="warp_surface", action='store_true',
31+
help="warp surface by upperSurface variable. " \
32+
"Uses scale factor from -s. " \
33+
"If upperSurface is not in file, will attempt " \
34+
"to calculate it from thickness and bedTopography. " \
35+
"Uses geometry from first time level only; it is not " \
36+
"possible to have scaling evolve in time with this method.")
37+
parser.add_argument("-s", dest="scale", type=float, default=100.,
38+
help="scale factor for warping surface")
39+
args = parser.parse_args()
40+
41+
infile = Path(args.infile)
42+
if args.outfile is None:
43+
outfile = infile.with_name(f'{infile.stem}_sphere{infile.suffix}')
44+
else:
45+
outfile = Path(args.outfile)
46+
47+
if infile.resolve() == outfile.resolve():
48+
raise ValueError('Output file must be different from input file.')
49+
50+
shutil.copyfile(infile, outfile)
51+
52+
53+
def compute_xyz(latCell_deg, lonCell_deg, radius):
54+
'''
55+
Copied from mpas_tools.mesh.creation.util.lonlat2xyz in
56+
conda_package/mpas_tools/mesh/creation/util.py
57+
'''
58+
lat_rad = np.radians(latCell_deg)
59+
lon_rad = np.radians(lonCell_deg)
60+
61+
xCell = radius * np.cos(lat_rad) * np.cos(lon_rad)
62+
yCell = radius * np.cos(lat_rad) * np.sin(lon_rad)
63+
zCell = radius * np.sin(lat_rad)
64+
65+
return xCell, yCell, zCell
66+
67+
68+
with Dataset(outfile, "r+") as ds:
69+
latCell = np.degrees(ds.variables["latCell"][:])
70+
lonCell = np.degrees(ds.variables["lonCell"][:])
71+
72+
if args.warp_surface:
73+
has_upper_surface = 'upperSurface' in ds.variables
74+
has_thickness = 'thickness' in ds.variables
75+
has_bed_topography = 'bedTopography' in ds.variables
76+
77+
if not (has_upper_surface or (has_thickness and has_bed_topography)):
78+
raise ValueError(
79+
"Cannot warp surface: provide 'upperSurface' or both "
80+
"'thickness' and 'bedTopography'.")
81+
82+
if has_upper_surface:
83+
sfc = ds.variables['upperSurface'][0,:]
84+
else:
85+
thickness = ds.variables['thickness'][0,:]
86+
bedTopography = ds.variables['bedTopography'][0,:]
87+
sfc = np.maximum(bedTopography + thickness,
88+
(1.0 - 910.0 / 1028.0) * thickness)
89+
radius = args.radius + np.maximum(sfc, 0.0) * args.scale
90+
else:
91+
radius = args.radius
92+
xCell, yCell, zCell = compute_xyz(latCell, lonCell, radius)
93+
94+
# Add to NetCDF file (assumes variables already exist)
95+
ds.variables["xCell"][:] = xCell
96+
ds.variables["yCell"][:] = yCell
97+
ds.variables["zCell"][:] = zCell
98+
# Update global attributes
99+
ds.setncattr("on_a_sphere", "YES")
100+
ds.setncattr("sphere_radius", args.radius)
101+
102+
# Record this modification in the global history attribute
103+
prev_history = getattr(ds, "history", "")
104+
cmd = Path(__file__).name + " " + " ".join(sys.argv[1:])
105+
cmd = cmd.strip()
106+
if prev_history:
107+
new_history = prev_history + "\n" + cmd
108+
else:
109+
new_history = cmd
110+
ds.setncattr("history", new_history)

0 commit comments

Comments
 (0)