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