-
Notifications
You must be signed in to change notification settings - Fork 7
Cice grid generation #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
b53e07d
9a1682f
4a6e8e4
75f4353
01ceaa6
177d807
d1b0cdb
7d6bec5
bf45b0a
e33d9d1
1b5c8e3
f500eb7
04cc749
a93cdbb
871d410
c505109
f0e9cfd
819204c
469ecfc
e1296bc
b518fbd
bc6fadc
cf57e0c
029b399
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import numpy as np | ||
| import netCDF4 as nc | ||
|
|
||
| from .base_grid import BaseGrid | ||
| from esmgrids.base_grid import BaseGrid | ||
| from esmgrids.mom_grid import MomGrid | ||
| from esmgrids.util import * | ||
|
|
||
|
|
||
| class CiceGrid(BaseGrid): | ||
|
|
@@ -65,66 +67,105 @@ def fromfile(cls, h_grid_def, mask_file=None, description="CICE tripolar"): | |
| description=description, | ||
| ) | ||
|
|
||
| def write(self, grid_filename, mask_filename): | ||
| def create_gridnc(self, grid_filename): | ||
| self.grid_f = create_nc(grid_filename) | ||
| return True | ||
|
|
||
| def create_masknc(self, mask_filename): | ||
| self.mask_f = create_nc(mask_filename) | ||
| return True | ||
|
|
||
| def create_2d_grid_var(self, name): | ||
| # set chunksizes based on OM2 config | ||
| # To-do: load these from a configuration file? | ||
| if self.num_lon_points == 360: # 1deg | ||
| chunksizes = (150, 180) | ||
| elif self.num_lon_points == 1440: # 0.25deg | ||
| chunksizes = (540, 720) | ||
| elif self.num_lon_points == 3600: # 0.01deg | ||
| chunksizes = (270, 360) | ||
| else: | ||
| chunksizes = None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should remove this hardcoding. What's wrong with using the default chunksize?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just used these to be consistent with the rest of the om2 cice output. There's a slim chance it helps with mapping reading the file to the right PE's. It also might make it easier to merge with this with the output data.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok good. This is done through your PR |
||
|
|
||
| return self.grid_f.createVariable( | ||
| name, | ||
| "f8", | ||
| dimensions=("ny", "nx"), | ||
| compression="zlib", | ||
| complevel=1, | ||
| chunksizes=chunksizes, | ||
| ) | ||
|
|
||
| def write(self): | ||
| """ | ||
| Write out CICE grid to netcdf. | ||
| """ | ||
|
|
||
| f = nc.Dataset(grid_filename, "w") | ||
| f = self.grid_f | ||
|
|
||
| # Create dimensions. | ||
| f.createDimension("nx", self.num_lon_points) | ||
| # nx is the grid_longitude but doesn't have a value other than its index | ||
| f.createDimension("ny", self.num_lat_points) | ||
| f.createDimension("nc", 4) | ||
| # ny is the grid_latitude but doesn't have a value other than its index | ||
|
|
||
| # Make all CICE grid variables. | ||
| ulat = f.createVariable("ulat", "f8", dimensions=("ny", "nx")) | ||
| # names are based on https://cfconventions.org/Data/cf-standard-names/current/build/cf-standard-name-table.html | ||
| f.Conventions = "CF-1.6" | ||
|
|
||
| ulat = self.create_2d_grid_var("ulat") | ||
| ulat.units = "radians" | ||
| ulat.title = "Latitude of U points" | ||
| ulon = f.createVariable("ulon", "f8", dimensions=("ny", "nx")) | ||
| ulat.long_name = "Latitude of U points" | ||
| ulat.standard_name = "latitude" | ||
| ulon = self.create_2d_grid_var("ulon") | ||
| ulon.units = "radians" | ||
| ulon.title = "Longitude of U points" | ||
| tlat = f.createVariable("tlat", "f8", dimensions=("ny", "nx")) | ||
| ulon.long_name = "Longitude of U points" | ||
| ulon.standard_name = "longitude" | ||
| tlat = self.create_2d_grid_var("tlat") | ||
| tlat.units = "radians" | ||
| tlat.title = "Latitude of T points" | ||
| tlon = f.createVariable("tlon", "f8", dimensions=("ny", "nx")) | ||
| tlat.long_name = "Latitude of T points" | ||
| tlat.standard_name = "latitude" | ||
| tlon = self.create_2d_grid_var("tlon") | ||
| tlon.units = "radians" | ||
| tlon.title = "Longitude of T points" | ||
|
|
||
| if self.clon_t is not None: | ||
| clon_t = f.createVariable("clon_t", "f8", dimensions=("nc", "ny", "nx")) | ||
| clon_t.units = "radians" | ||
| clon_t.title = "Longitude of T cell corners" | ||
| clat_t = f.createVariable("clat_t", "f8", dimensions=("nc", "ny", "nx")) | ||
| clat_t.units = "radians" | ||
| clat_t.title = "Latitude of T cell corners" | ||
| clon_u = f.createVariable("clon_u", "f8", dimensions=("nc", "ny", "nx")) | ||
| clon_u.units = "radians" | ||
| clon_u.title = "Longitude of U cell corners" | ||
| clat_u = f.createVariable("clat_u", "f8", dimensions=("nc", "ny", "nx")) | ||
| clat_u.units = "radians" | ||
| clat_u.title = "Latitude of U cell corners" | ||
|
Comment on lines
-94
to
-106
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why has this been removed? Will this potentially break existing uses of the code?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not used in CICE5 or CICE6. Maybe it was just added to be consistent with all the other grid types in the package? I think because its not used it just adds confusion rather than value. |
||
|
|
||
| htn = f.createVariable("htn", "f8", dimensions=("ny", "nx")) | ||
| tlon.long_name = "Longitude of T points" | ||
| tlon.standard_name = "longitude" | ||
|
|
||
| htn = self.create_2d_grid_var("htn") | ||
| htn.units = "cm" | ||
| htn.title = "Width of T cells on North side." | ||
| hte = f.createVariable("hte", "f8", dimensions=("ny", "nx")) | ||
| htn.long_name = "Width of T cells on North side." | ||
| htn.coordinates = "ulat tlon" | ||
| htn.grid_mapping = "crs" | ||
| hte = self.create_2d_grid_var("hte") | ||
| hte.units = "cm" | ||
| hte.title = "Width of T cells on East side." | ||
| hte.long_name = "Width of T cells on East side." | ||
| hte.coordinates = "tlat ulon" | ||
| hte.grid_mapping = "crs" | ||
|
|
||
| angle = f.createVariable("angle", "f8", dimensions=("ny", "nx")) | ||
| angle = self.create_2d_grid_var("angle") | ||
| angle.units = "radians" | ||
| angle.title = "Rotation angle of U cells." | ||
| angleT = f.createVariable("angleT", "f8", dimensions=("ny", "nx")) | ||
| angle.long_name = "Rotation angle of U cells." | ||
| angle.standard_name = "angle_of_rotation_from_east_to_x" | ||
| angle.coordinates = "ulat ulon" | ||
| angle.grid_mapping = "crs" | ||
| angleT = self.create_2d_grid_var("angleT") | ||
| angleT.units = "radians" | ||
| angleT.title = "Rotation angle of T cells." | ||
| angleT.long_name = "Rotation angle of T cells." | ||
| angleT.standard_name = "angle_of_rotation_from_east_to_x" | ||
| angleT.coordinates = "tlat tlon" | ||
| angleT.grid_mapping = "crs" | ||
|
|
||
| area_t = f.createVariable("tarea", "f8", dimensions=("ny", "nx")) | ||
| area_t = self.create_2d_grid_var("tarea") | ||
| area_t.units = "m^2" | ||
| area_t.title = "Area of T cells." | ||
| area_u = f.createVariable("uarea", "f8", dimensions=("ny", "nx")) | ||
| area_t.long_name = "Area of T cells." | ||
| area_t.standard_name = "cell_area" | ||
| area_t.coordinates = "tlat tlon" | ||
| area_t.grid_mapping = "crs" | ||
| area_u = self.create_2d_grid_var("uarea") | ||
| area_u.units = "m^2" | ||
| area_u.title = "Area of U cells." | ||
| area_u.long_name = "Area of U cells." | ||
| area_u.standard_name = "cell_area" | ||
| area_u.coordinates = "ulat ulon" | ||
| area_u.grid_mapping = "crs" | ||
|
|
||
| area_t[:] = self.area_t[:] | ||
| area_u[:] = self.area_u[:] | ||
|
|
@@ -135,12 +176,6 @@ def write(self, grid_filename, mask_filename): | |
| ulat[:] = np.deg2rad(self.y_u) | ||
| ulon[:] = np.deg2rad(self.x_u) | ||
|
|
||
| if self.clon_t is not None: | ||
| clon_t[:] = np.deg2rad(self.clon_t) | ||
| clat_t[:] = np.deg2rad(self.clat_t) | ||
| clon_u[:] = np.deg2rad(self.clon_u) | ||
| clat_u[:] = np.deg2rad(self.clat_u) | ||
|
Comment on lines
-138
to
-142
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, does this really need to be removed? |
||
|
|
||
| # Convert from m to cm. | ||
| htn[:] = self.dx_tn[:] * 100.0 | ||
| hte[:] = self.dy_te[:] * 100.0 | ||
|
|
@@ -150,9 +185,68 @@ def write(self, grid_filename, mask_filename): | |
|
|
||
| f.close() | ||
|
|
||
| with nc.Dataset(mask_filename, "w") as f: | ||
| f.createDimension("nx", self.num_lon_points) | ||
| f.createDimension("ny", self.num_lat_points) | ||
| mask = f.createVariable("kmt", "f8", dimensions=("ny", "nx")) | ||
| # CICE uses 0 as masked, whereas internally we use 1 as masked. | ||
| mask[:] = 1 - self.mask_t | ||
| def write_mask(self): | ||
| """ | ||
| Write out CICE mask/kmt to netcdf. | ||
| """ | ||
|
|
||
| f = self.mask_f | ||
| f.createDimension("nx", self.num_lon_points) | ||
| f.createDimension("ny", self.num_lat_points) | ||
| mask = f.createVariable("kmt", "f8", dimensions=("ny", "nx"), compression="zlib", complevel=1) | ||
|
|
||
| mask.grid_mapping = "crs" | ||
| mask.standard_name = "sea_binary_mask" | ||
|
|
||
| # CICE uses 0 as masked, whereas internally we use 1 as masked. | ||
| mask[:] = 1 - self.mask_t | ||
| f.close() | ||
|
|
||
|
|
||
| def cice_from_mom(ocean_hgrid, ocean_mask, grid_file="grid.nc", mask_file="kmt.nc"): | ||
|
|
||
| mom = MomGrid.fromfile(ocean_hgrid, mask_file=ocean_mask) | ||
|
|
||
| cice = CiceGrid.fromgrid(mom) | ||
|
|
||
| cice.create_gridnc(grid_file) | ||
|
|
||
| # Add versioning information | ||
| cice.grid_f.inputfile = f"{ocean_hgrid}" | ||
| cice.grid_f.inputfile_md5 = md5sum(ocean_hgrid) | ||
| cice.grid_f.history_command = f"python make_CICE_grid.py {ocean_hgrid} {ocean_mask}" | ||
|
|
||
| # Add the typical crs (i.e. WGS84/EPSG4326 , but in radians). | ||
| crs = cice.grid_f.createVariable("crs", "S1") | ||
| crs.grid_mapping_name = "tripolar_latitude_longitude" | ||
| crs.crs_wkt = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["radians",1,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]' | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I definitely don't understand the details of CRSs. Is "tripolar_latitude_longitude" really a valid grid mapping? I can't find anything about this in the CF conventions Is this CRS metadata really valid generally, or are there assumptions about the input grid here? Incorrect CRS info is arguably worse than no CRS info
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is tricky. Our data relies on having 2d arrays of lat/lon (i.e. geolocation arrays) to be used. I haven't found a way to describe it using a proj code or a grid_mapping description. So the crs here applies to the geolocation arrays. Without the geolocation arrays it doesn't really make sense. So its the crs_wkt that matters here, thats the information that could (in theory) be used by some piece of analysis software. I just tried to give a descriptive grid_mapping name. The assumptions are that the input grid is a tripolar input grid in WGS84. The whole thing falls over if that is not true because of the wrapping and folding of the grid. So one day we will need to extend this code for regional grids.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
So should the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok done |
||
|
|
||
| cice.write() | ||
|
|
||
| cice.create_masknc(mask_file) | ||
|
|
||
| # Add versioning information | ||
| cice.mask_f.inputfile = f"{ocean_mask}" | ||
| cice.mask_f.inputfile_md5 = md5sum(ocean_mask) | ||
| cice.mask_f.history_command = f"python make_CICE_grid.py {ocean_hgrid} {ocean_mask}" | ||
|
|
||
| # Add the typical crs (i.e. WGS84/EPSG4326 , but in radians). | ||
| crs = cice.mask_f.createVariable("crs", "S1") | ||
| crs.grid_mapping_name = "tripolar_latitude_longitude" | ||
| crs.crs_wkt = 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["radians",1,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]' | ||
|
|
||
| cice.write_mask() | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that it's nice to provide a script to generate a CICE grid from a MOM grid, but I don't think this is the right place to house this code. The grid classes are written to enable conversion between all different types of implemented grids. Scripts to do conversions between specific grids should live elsewhere. These can also be registered as
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok good. This is done through your PR |
||
| import argparse | ||
| import sys | ||
|
|
||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("ocean_hgrid", help="ocean_hgrid.nc file") | ||
| parser.add_argument("ocean_mask", help="ocean_mask.nc file") | ||
| # to-do: add argument for CRS & output filenames? | ||
|
|
||
| args = vars(parser.parse_args()) | ||
|
|
||
| sys.exit(cice_from_mom(**args)) | ||
Uh oh!
There was an error while loading. Please reload this page.