Skip to content
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

Proper metadata for CICE grid files #7

Closed
aidanheerdegen opened this issue Mar 1, 2024 · 30 comments
Closed

Proper metadata for CICE grid files #7

aidanheerdegen opened this issue Mar 1, 2024 · 30 comments
Assignees

Comments

@aidanheerdegen
Copy link

CICE grid.nc files have historically had poor CF compliant metadata.

Case in point, this 1 degree CICE grid /g/data/vk83/experiments/inputs/access-om2/ice/grids/global.1deg/2020.05.30/grid.nc (same file as /g/data/ik11/inputs/access-om2/input_20200530/cice_1deg/grid.nc).

cf_xarray is completely unable to intuit any CF coordinate information:

>>> import xarray, cf_xarray
>>> ds = xarray.open_dataset('/g/data/vk83/experiments/inputs/access-om2/ice/grids/global.1deg/2020.05.30/grid.nc')
>>> ds.cf
Coordinates:
             CF Axes:   X, Y, Z, T: n/a

      CF Coordinates:   longitude, latitude, vertical, time: n/a

       Cell Measures:   area, volume: n/a

      Standard Names:   n/a

              Bounds:   n/a

       Grid Mappings:   n/a

Data Variables:
       Cell Measures:   area, volume: n/a

      Standard Names:   n/a

              Bounds:   n/a

       Grid Mappings:   n/a
>>> 

While work is being done to update the grids the metadata should be improved.

@navidcy
Copy link

navidcy commented Mar 1, 2024

I applaud this.

@aidanheerdegen
Copy link
Author

It would be good to get @flicj191's opinion about adding a CRS string, and what would be appropriate

https://en.wikipedia.org/wiki/Spatial_reference_system

@flicj191
Copy link

flicj191 commented Mar 3, 2024

Though I'm not sure of the detail here, I'd say you'd want to keep CRS information so you can map, transform and work with other data etc. Is the CF standard for lat/lon coordinate system WGS84 and that would be assumed here?

And also when adding a CRS string you'd use a spatial library to get the full CRS definition and can refer using a short name or maybe epsg code eg 4326 but in other files I've seen its mainly been a name of the CRS then using a python spatial library if anything needs to be done to it.

@anton-seaice
Copy link
Contributor

Though I'm not sure of the detail here, I'd say you'd want to keep CRS information so you can map, transform and work with other data etc. Is the CF standard for lat/lon coordinate system WGS84 and that would be assumed here?

And also when adding a CRS string you'd use a spatial library to get the full CRS definition and can refer using a short name or maybe epsg code eg 4326 but in other files I've seen its mainly been a name of the CRS then using a python spatial library if anything needs to be done to it.

Some of the data available through cj50/ the nci thredds server is marked with 4326, so I would assume that's correct. I was surprised because I thought the tripole grid might mess with this, but apparently not. I guess the SRS was added to make the OMIP output a bit more tidy, but well need to double check it.

@anton-seaice
Copy link
Contributor

@aidanheerdegen Do you know how to do this? i.e.- is there a package we should use to add the CF-metadata or add it "by-hand" as attributes

@aidanheerdegen
Copy link
Author

aidanheerdegen commented Mar 5, 2024

Do you know how to do this? i.e.- is there a package we should use to add the CF-metadata or add it "by-hand" as attributes

I do not.

@bschroeter may have opinions, he wrote this:

https://github.com/AusClimateService/axiom

And @flicj191 is our resident GIS expert, so is probably the best placed to comment on what is required for our data to be usable in geospatial applications.

@mdsumner
Copy link

mdsumner commented Mar 5, 2024

If I understand correctly, it's the grid mapping that is required, and so as per 5.8 or 5.9 here:

https://cfconventions.org/Data/cf-conventions/cf-conventions-1.7/build/ch05s06.html

there's a dummy scalar int var "crs" where you put the datum details (or otherwise a named var where to put further params for an actual coordinate system). Geospatial folks would wish that simply the full WKT was always stored rather than being atomized into parameters, but I think that's too much to wish for given that PROJ strings ruled the waves until PROJ library version 6 established a new norm (you need the full specification and certain assumptions from short forms are deprecated or at least frowned upon).

There's multiple pairs of lon,lat in the grid, and so I guess you specify on any variable that uses those the "grid_mapping" attribute, and it can stand in for the details of any lon, lat pairing. Can someone show me a file that uses the grid in /g/data/ik11/inputs/access-om2/input_20200530/cice_1deg/grid.nc ? I presume that this grid.nc is a template of some sort, are the actual values propagated down into output files, or is this input grid kept as a reference downstream? I'm a bit lost in how this file affects published outputs.

I will try adding the attributes as I understand them and test in various geo contexts.

And, I must admit I'm not much of a CF-adherent, more of a "how can I get geospatial to interpret this ..."-adherent, and I've added a few PRs to GDAL itself to enable some workarounds, but that can be pretty slow and maybe too conservative.

So, apologies if this is noise and I'm just complicating it, but fwiw I wrote a hand-crafted request that they add similar grid mapping to the daily 0.25 Reynolds OISST (they replied and said it would be considered for future updates):

https://github.com/mdsumner/fixoisst

Also, this is my little patch to GDAL to allow configuring "assume the thing has a longlat crs ...", but it's a bit too conservative, misses important cases (, and who will even find out about it anyway ... but, it's useful to me now).

OSGeo/gdal#6910

This for me is one of those "who can even talk to about this ..." topics, so I appreciate being brought in and maybe I can help a little. 🙏 (it's also an issue before we get to the part about how a geospatial raster can be derived from this non-regular grid, but that's relatively easy and quite separate to the main topic here, I think).

@anton-seaice
Copy link
Contributor

Thanks Mike!

There are two things we needed to add:

  1. the cf-compliant metadata for coordinates/variables (i.e. lat/lon/area/dx/dy). This looks straightforward enough by adding standard_name and units attributes ( e.g. https://climate-cms.org/posts/2018-10-26-Setting-up-NetCDF-file-attributes.html#variable-attributes). Although it looks like having lat/lon in radians (per CICE convention) is not allowed by the cf-standard-name-table
  2. the grid mapping / datum / crs for the grid information the file is describing. From that link it looks like we need a dummy variable, something like:
int crs ;
    crs:grid_mapping_name = "latitude_longitude"
    crs:crs_wkt ... (WGS84)

There is nothing there to capture the tripole ... maybe that is ok.

There's multiple pairs of lon,lat in the grid, and so I guess you specify on any variable that uses those the "grid_mapping" attribute, and it can stand in for the details of any lon, lat pairing.

Will having multiple variables identified as lat/lon can be annoying for analysis? Most of our output variables are at the T points but others are at the U points (I guess we need to make sure we are encoding this correctly in the output - it is sort of in the cell_measures attribute from OM2).

I presume that this grid.nc is a template of some sort, are the actual values propagated down into output files, or is this input grid kept as a reference downstream?

The actual values are propagated in the output files, but not in a particularly useful way, as a land(-ish)-mask has been applied (i.e. it doesn't save the values for the cells where no calculations were done).
Screenshot 2024-03-06 at 9 49 40 am
(Example from /g/data/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc, / cell 7 of https://cosima-recipes.readthedocs.io/en/latest/Tutorials/Spatial_selection.html)

So, apologies if this is noise and I'm just complicating it, but fwiw I wrote a hand-crafted request that they add similar grid mapping to the daily 0.25 Reynolds OISST (they replied and said it would be considered for future updates):

https://github.com/mdsumner/fixoisst

This seems similar to the ESMValTool CMORise step to add the CF metadata and standardise the units, although they don't seem too worried about the projection information (I guess its basically always the same in our usecase).

@mdsumner
Copy link

appreciate the answers, and questions - I'll get back to this at a later time just can't do it atm

@anton-seaice
Copy link
Contributor

anton-seaice commented Mar 19, 2024

I realised that using radians for longitude and latitude is not CF-compliant, so that doesn't help our cause. We could:

  • add more variables to the grid file so there is a version of lat/lon in degrees and could use the standard_name='latitude'/'longitude'. (This is a bit weird becuase the model won't use the added variables) or
  • make another 'ice_grid' file which is formatted nicely for post-processing (similar to the 'ocean_grid_...' files in /g/data/ik11/grids/)

Edit: or we could just label them using the standard_name='latitude'/'longitude' as a best effort compiance rather than a strict compliance

@mdsumner
Copy link

I haven't quite had a chance to get back to this, will have a closer look soon

@mdsumner
Copy link

I think you're right about the crs dummy var, that's enough as far as I'm concerned. I see that the relationship is already there from a GDAL perspective though, because it's recorded in the "coordinates" attribute. GDAL picks up TLON and TLAT or ULON and ULAT appropriately as the geolocation arrays:

gdalinfo NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":hi_m | grep Geolocation -A 10
Geolocation:
  GEOREFERENCING_CONVENTION=PIXEL_CENTER
  LINE_OFFSET=0
  LINE_STEP=1
  PIXEL_OFFSET=0
  PIXEL_STEP=1
  SRS=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["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
  X_BAND=1
  X_DATASET=NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":TLON
  Y_BAND=1
  Y_DATASET=NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":TLAT

gdalinfo NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":uvel_m | grep Geolocation -A 10
Geolocation:
  GEOREFERENCING_CONVENTION=PIXEL_CENTER
  LINE_OFFSET=0
  LINE_STEP=1
  PIXEL_OFFSET=0
  PIXEL_STEP=1
  SRS=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["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
  X_BAND=1
  X_DATASET=NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":ULON
  Y_BAND=1
  Y_DATASET=NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":ULAT

(I'm using the fileServer because I can't get on gadi rn).

So I don't know how import the grid mapping is in comparison, but if your changes are adding CF-compliance I think that's sufficient. Using the warp API with those sources will automatically resolve to a 0,360 longlat regular grid, or to a target spec (more important for downstream issues is the appropriateness or otherwise for what that means for the quantities when rotation from the native is involved - I'd like this to get attention in GDAL itself, or at least have warnings issued).

At this stage I feel like I'm mostly just following along as you go here, but a chat in person would be good at some point. We can learn a lot from the experiences here.

@anton-seaice
Copy link
Contributor

That is mighty interesting. I can't see anything in /g/data/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc to show how gdal has decided in WGS 84.

rioxarray for example has not picked up any georeferencing.

It looks like maybe it has guessed? To quote the link:

If "grid_mapping" is not present, the driver will try to find an lat/lon grid array to set geotransform array. The NetCDF driver verifies that the Lat/Lon array is equally spaced.

There are multiple ways to add the grid projection, but I'll try using the cf preferred method.

@mdsumner
Copy link

mdsumner commented Mar 20, 2024

that's a good point, this is TLON and TLAT as data and without that dummy "crs" attribute it doesn't have it stored anywhere (i.e. what if it's degrees_east/west spherical?). I'll dig into this.

There are other contexts where the NetCDF driver doesn't helpfully assume that it's a longlat crs for the regular grid derived from lon/lat 1D arrays (basic OISST netcdf files do not, for example).

@anton-seaice
Copy link
Contributor

I tried setting the crs like this:

        GEOGCRS["WGS 84",
            ENSEMBLE["World Geodetic System 1984 ensemble",
                MEMBER["World Geodetic System 1984 (Transit)"],
                MEMBER["World Geodetic System 1984 (G730)"],
                MEMBER["World Geodetic System 1984 (G873)"],
                MEMBER["World Geodetic System 1984 (G1150)"],
                MEMBER["World Geodetic System 1984 (G1674)"],
                MEMBER["World Geodetic System 1984 (G1762)"],
                MEMBER["World Geodetic System 1984 (G2139)"],
                ELLIPSOID["WGS 84",6378137,298.257223563,
                    LENGTHUNIT["metre",1]],
                ENSEMBLEACCURACY[2.0]],
            PRIMEM["Greenwich",0,
                ANGLEUNIT["radian",1]],
            CS[ellipsoidal,2],
                AXIS["geodetic latitude (Lat)",north,
                    ORDER[1],
                    ANGLEUNIT["radian",1]],
                AXIS["geodetic longitude (Lon)",east,
                    ORDER[2],
                    ANGLEUNIT["radian",1]],
            USAGE[
                SCOPE["Horizontal component of 3D system."],
                AREA["World."],
                BBOX[-80,80,90,80]],
            ID["EPSG",4326]]

But im not sure it helped, rioxarray doesn't do anything clever with it. GDAL doesn't complain, but are these corners correct?

Geolocation:
  GEOREFERENCING_CONVENTION=PIXEL_CENTER
  LINE_OFFSET=0
  LINE_STEP=1
  PIXEL_OFFSET=0
  PIXEL_STEP=1
  SRS=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["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
  X_BAND=1
  X_DATASET=NETCDF:"01deg/grid.nc":ulon
  Y_BAND=1
  Y_DATASET=NETCDF:"01deg/grid.nc":ulat
Corner Coordinates:
Upper Left  (    0.0,    0.0)
Lower Left  (    0.0, 2700.0)
Upper Right ( 3600.0,    0.0)
Lower Right ( 3600.0, 2700.0)
Center      ( 1800.0, 1350.0)

I am still confused about the utility of adding a crs in this case though, the geolocation array is the source of truth. We don't expect a program to be able to location the grid based only on the crs.

@mdsumner
Copy link

in GDAL context this is "ungeoreferenced", only the potential is there for creating a traditional raster from it because of the references to those geolocation array. ( I had meant to tell this part of the story a bit more ... it's why from my perspective it's only the CF compliance that really matters for your purposes, because without actually regridding or turning this into a point data set there's not much here for a straight read into geospatial). For geospatial it's already in good shape (for the next step)
as the geolocation arrays are recognized.

To materialize a regular grid or write a virtual file we need the warper:

gdalwarp  NETCDF:"/vsicurl/https://dapds00.nci.org.au/thredds/fileServer/cj50/access-om2/raw-output/access-om2-01/01deg_jra55v140_iaf/output000/ice/OUTPUT/iceh.1958-01.nc":hi_m    regular0_360.vrt -te 0 -90 360 90  -tr 1 1

that will then be understood by rioxarray or QGIS etc:

gdalinfo regular0_360.vrt -nomd
Driver: VRT/Virtual Raster
Files: regular0_360.vrt
Size is 360, 180
Coordinate System is:
GEOGCRS["WGS 84",
    DATUM["World Geodetic System 1984",
        ELLIPSOID["WGS 84",6378137,298.257223563,
            LENGTHUNIT["metre",1]]],
    PRIMEM["Greenwich",0,
        ANGLEUNIT["degree",0.0174532925199433]],
    CS[ellipsoidal,2],
        AXIS["geodetic latitude (Lat)",north,
            ORDER[1],
            ANGLEUNIT["degree",0.0174532925199433]],
        AXIS["geodetic longitude (Lon)",east,
            ORDER[2],
            ANGLEUNIT["degree",0.0174532925199433]],
    ID["EPSG",4326]]
Data axis to CRS axis mapping: 2,1
Origin = (0.000000000000000,90.000000000000000)
Pixel Size = (1.000000000000000,-1.000000000000000)

Corner Coordinates:
Upper Left  (   0.0000000,  90.0000000) (  0d 0' 0.01"E, 90d 0' 0.00"N)
Lower Left  (   0.0000000, -90.0000000) (  0d 0' 0.01"E, 90d 0' 0.00"S)
Upper Right (     360.000,      90.000) (360d 0' 0.00"E, 90d 0' 0.00"N)
Lower Right (     360.000,     -90.000) (360d 0' 0.00"E, 90d 0' 0.00"S)
Center      ( 180.0000000,   0.0000000) (180d 0' 0.00"E,  0d 0' 0.01"N)
Band 1 Block=360x128 Type=Float32, ColorInterp=Undefined
  NoData Value=1e+30
  Unit Type: m
  Metadata:
    cell_measures=area: tarea
    cell_methods=time: mean
    coordinates=TLON TLAT time
    long_name=grid cell mean ice thickness
    missing_value=1e+30
    NETCDF_DIM_time=31
    NETCDF_VARNAME=hi_m
    time_rep=averaged
    units=m
    _FillValue=1e+30

rioxarray and other cubey-friends do this warping task when resolving Sentinel and Landsat from different crs, but they don't yet apply that resource to climate grids like these or xarray generally.

the low-res result as a "geospatial raster" image

library(terra)

(r <- rast("~/regular0_360.vrt"))
class       : SpatRaster 
dimensions  : 180, 360, 1  (nrow, ncol, nlyr)
resolution  : 1, 1  (x, y)
extent      : 0, 360, -90, 90  (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (EPSG:4326) 
source      : regular0_360.vrt 
name        : regular0_360

image

@anton-seaice
Copy link
Contributor

Yeah ok.

So the important bit is specifying the coordinates attribute for each variable, so gdal can auto-detect those? And then we should also have the CRS for completeness, so its known 'how' the values of those coordinates are defined.

This seems a bit counter-intuitive ... the CRS is not for the model grid or the netcdf dimensions, its for the coordinates (which are just variables) however the CRS is defined as a global attribute for the netcdf file.

@mdsumner
Copy link

mdsumner commented Mar 20, 2024

it is counter intuitive indeed, there's no geospatial standard for it (sadly, because the software available is pretty good). I think of it as a raster but the coordinates are also data, it's not just "points", but nothing in trad geo really handles it (though, weirdly modern cube tooling has gone ballistic on it because of all those utm crs grids) Closest to a standard is probably mdal, that at least is well supported in QGIS. (what I'm not clear on yet is if the QGIS map will automatically resolve these Cosima grids, it might - and if not it won't take much, a thought I just had).

@anton-seaice
Copy link
Contributor

Hi Mike. Thanks for all you help on this.

Do you know how to set the SRS for the geolocation array? I set grid_mapping for the variables, and the crs_wkt for that grid mapping ... but they haven't affected the geolocation array!

See below ...

Coordinate System is set

Geolocation SRS is set differently! (See lines in Bold)

! gdalinfo NETCDF:1deg/grid.nc:uarea

Driver: netCDF/Network Common Data Format
Files: 1deg/grid.nc
Size is 360, 300
Coordinate System is:
GEOGCRS["WGS 84",
ENSEMBLE["World Geodetic System 1984 ensemble",
MEMBER["World Geodetic System 1984 (Transit)"],
MEMBER["World Geodetic System 1984 (G730)"],
MEMBER["World Geodetic System 1984 (G873)"],
MEMBER["World Geodetic System 1984 (G1150)"],
MEMBER["World Geodetic System 1984 (G1674)"],
MEMBER["World Geodetic System 1984 (G1762)"],
MEMBER["World Geodetic System 1984 (G2139)"],
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]],
ENSEMBLEACCURACY[2.0]],
PRIMEM["Greenwich",0,
ANGLEUNIT["radian",1]],
CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,
ORDER[1],
ANGLEUNIT["radian",1]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2],
ANGLEUNIT["radian",1]],
USAGE[
SCOPE["Horizontal component of 3D system."],
AREA["World."],
BBOX[-80,80,90,80]]]
Data axis to CRS axis mapping: 2,1
Metadata:
crs#crs_wkt=
GEOGCRS["WGS 84",
ENSEMBLE["World Geodetic System 1984 ensemble",
MEMBER["World Geodetic System 1984 (Transit)"],
MEMBER["World Geodetic System 1984 (G730)"],
MEMBER["World Geodetic System 1984 (G873)"],
MEMBER["World Geodetic System 1984 (G1150)"],
MEMBER["World Geodetic System 1984 (G1674)"],
MEMBER["World Geodetic System 1984 (G1762)"],
MEMBER["World Geodetic System 1984 (G2139)"],
ELLIPSOID["WGS 84",6378137,298.257223563,
LENGTHUNIT["metre",1]],
ENSEMBLEACCURACY[2.0]],
PRIMEM["Greenwich",0,
ANGLEUNIT["radian",1]],
CS[ellipsoidal,2],
AXIS["geodetic latitude (Lat)",north,
ORDER[1],
ANGLEUNIT["radian",1]],
AXIS["geodetic longitude (Lon)",east,
ORDER[2],
ANGLEUNIT["radian",1]],
USAGE[
SCOPE["Horizontal component of 3D system."],
AREA["World."],
BBOX[-80,80,90,80]],
ID["EPSG",4326]]

crs#grid_mapping_name=tripolar_latitude_longitude
NC_GLOBAL#created_by=as2285
NC_GLOBAL#history=Created using commit 386b73e of [email protected]:COSIMA/om3-scripts.git
NC_GLOBAL#history_command=python make_CICE_grid.py /g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_hgrid.nc /g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_mask.nc
NC_GLOBAL#inputfile=/g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_hgrid.nc
NC_GLOBAL#inputfile_md5=51f58be0f4ea6da2cb438a893f95c689
NC_GLOBAL#timeGenerated=2024-03-21 12:32:38.463132
uarea#coordinates=ulat ulon
uarea#grid_mapping=crs
uarea#long_name=Area of U cells.
uarea#standard_name=cell_area
uarea#units=m^2
Geolocation:
GEOREFERENCING_CONVENTION=PIXEL_CENTER
LINE_OFFSET=0
LINE_STEP=1
PIXEL_OFFSET=0
PIXEL_STEP=1
SRS=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["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]
X_BAND=1
X_DATASET=NETCDF:"1deg/grid.nc":ulon
Y_BAND=1
Y_DATASET=NETCDF:"1deg/grid.nc":ulat
Corner Coordinates:
Upper Left ( 0.0, 0.0)
Lower Left ( 0.0, 300.0)
Upper Right ( 360.0, 0.0)
Lower Right ( 360.0, 300.0)
Center ( 180.0, 150.0)
Band 1 Block=360x300 Type=Float64, ColorInterp=Undefined
NoData Value=9.969209968386869e+36
Unit Type: m^2
Metadata:
coordinates=ulat ulon
grid_mapping=crs
long_name=Area of U cells.
NETCDF_VARNAME=uarea
standard_name=cell_area
units=m^2

@mdsumner
Copy link

mdsumner commented Mar 21, 2024

I'll need to delve into a netcdf creation and investigation in GDAL itself, but - meta-question - why do you want a CRS that's using radians?

I'll delve into this, and try setting the crs in different ways - I assume you are setting a crs_wkt attribute property (not adding parameters one by one?), can you share the ncdump -h of your grid.nc?

@anton-seaice
Copy link
Contributor

I'll need to delve into a netcdf creation and investigation in GDAL itself, but - meta-question - why do you want a CRS that's using radians?

The CICE model uses input data that is in radians. The lon/lat included in the history output is in degrees, but it has a land mask applied, and having missing values doesn't play nice with analysis tools.

So having a file with the CRS set to load the grid from could be handy but maybe we don't need it. i.e. the GDAL defaults just work (as you've shown).

I'll delve into this, and try setting the crs in different ways - I assume you are setting a wkt attribute property (not adding parameters one by one?), can you share the ncdump -h of your grid.nc?

This is the ncdump output

netcdf grid {
dimensions:
	nx = 360 ;
	ny = 300 ;
variables:
	char crs ;
		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\"]]" ;
	double ulat(ny, nx) ;
		ulat:units = "radians" ;
		ulat:long_name = "Latitude of U points" ;
		ulat:standard_name = "latitude" ;
	double ulon(ny, nx) ;
		ulon:units = "radians" ;
		ulon:long_name = "Longitude of U points" ;
		ulon:standard_name = "longitude" ;
	double tlat(ny, nx) ;
		tlat:units = "radians" ;
		tlat:long_name = "Latitude of T points" ;
		tlat:standard_name = "latitude" ;
	double tlon(ny, nx) ;
		tlon:units = "radians" ;
		tlon:long_name = "Longitude of T points" ;
		tlon:standard_name = "longitude" ;
	double htn(ny, nx) ;
		htn:units = "cm" ;
		htn:long_name = "Width of T cells on North side." ;
		htn:coordinates = "tlat tlon" ;
		htn:grid_mapping = "crs" ;
	double hte(ny, nx) ;
		hte:units = "cm" ;
		hte:long_name = "Width of T cells on East side." ;
		hte:coordinates = "tlat tlon" ;
		hte:grid_mapping = "crs" ;
	double angle(ny, nx) ;
		angle:units = "radians" ;
		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" ;
	double angleT(ny, nx) ;
		angleT:units = "radians" ;
		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" ;
	double tarea(ny, nx) ;
		tarea:units = "m^2" ;
		tarea:long_name = "Area of T cells." ;
		tarea:standard_name = "cell_area" ;
		tarea:coordinates = "tlat tlon" ;
		tarea:grid_mapping = "crs" ;
	double uarea(ny, nx) ;
		uarea:units = "m^2" ;
		uarea:long_name = "Area of U cells." ;
		uarea:standard_name = "cell_area" ;
		uarea:coordinates = "ulat ulon" ;
		uarea:grid_mapping = "crs" ;

// global attributes:
		:timeGenerated = "2024-03-21 16:44:49.276378" ;
		:created_by = "as2285" ;
		:history = "Created using commit 386b73e43208414ba2185948c584aa4cf0bcb63b of [email protected]:COSIMA/om3-scripts.git" ;
		:inputfile = "/g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_hgrid.nc" ;
		:inputfile_md5 = "51f58be0f4ea6da2cb438a893f95c689" ;
		:history_command = "python make_CICE_grid.py /g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_hgrid.nc /g/data/ik11/inputs/access-om2/input_20201102/mom_1deg/ocean_mask.nc" ;
}

I've seperately also tried setting the SRS for the 'GEOLOCATION' metadata using GDAL, and saving to netcdf, and it looked liked this:

netcdf out {
dimensions:
	x = 360 ;
	y = 300 ;
variables:
	double uarea(y, x) ;
		uarea:long_name = "Area of U cells." ;
		uarea:_FillValue = 9.96920996838687e+36 ;
		uarea:grid_mapping = "crs" ;
		uarea:standard_name = "cell_area" ;
		uarea:units = "m^2" ;

// global attributes:
		:GDAL_LINE_OFFSET = 0 ;
		:GDAL_LINE_STEP = 1 ;
		:GDAL_PIXEL_OFFSET = 0 ;
		:GDAL_PIXEL_STEP = 1 ;
		:GDAL_SRS = "GEOGCS[WGS 84,DATUM[WGS_1984,SPHEROID[WGS 84,6378137,298.257223563,AUTHORITY[EPSG,7030]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY[EPSG,6326]],PRIMEM[Greenwich,0,AUTHORITY[EPSG,8901]],UNIT[degree,0.0174532925199433,AUTHORITY[EPSG,9108]],AXIS[Lat,NORTH],AXIS[Long,EAST],AUTHORITY[EPSG,4326]]" ;
		:GDAL_X_BAND = 1 ;
		:GDAL_X_DATASET = "/vsimem/test_netcdf_geolocation_array_no_srs_lon.tif" ;
		:GDAL_Y_BAND = 1 ;
		:GDAL_Y_DATASET = "/vsimem/test_netcdf_geolocation_array_no_srs_lat.tif" ;
		:Conventions = "CF-1.5" ;
		:GDAL = "GDAL 3.6.2, released 2023/01/02" ;
		:history = "Thu Mar 21 14:11:48 2024: GDAL CreateCopy( out.nc, ... )" ;
}

But setting GDAL_SRS as a global attribute in the netcdf file didn't seem to work the other way, the GDAL_SRS attribute wasn't read in to make the SRS for the Geolocation array.

@mdsumner
Copy link

I think there's a real problem here in gdal, I'll take my time to explore before I report 🙏

@anton-seaice
Copy link
Contributor

I think there's a real problem here in gdal, I'll take my time to explore before I report 🙏

Thankyou - it looks a bit odd but we are well into corner case territory here! I started look at the GDAL source but didn't dig too far.

There is this set of messages when I ran gdalinfo --config CPL_DEBUG ON :

SetProjectionFromVar( 65536, 10)
GDAL_netCDF: got grid_mapping crs
GDAL_netCDF: setting WKT from CF
GDAL_netCDF: bIsGdalFile=0 bIsGdalCfFile=0 bSwitchedXY=0 bBottomUp=1
GDAL_netCDF: using variables ulon and ulat for GEOLOCATION
GDAL_netCDF: bGotGeogCS=0 bGotCfSRS=0 bGotCfGT=0 bGotCfWktSRS=1 bGotGdalSRS=0 bGotGdalGT=0
GDAL_netCDF: did not get geotransform from CF nor GDAL!

@mdsumner
Copy link

I think it's a bug here, hardcoded WGS84 SRS: https://github.com/OSGeo/gdal/blob/35c434527aab4acb86af66480db00d4fec4b5400/frmts/netcdf/netcdfdataset.cpp#L4805

I've got a bit more to do to demonstrate it properly though ...

@mdsumner
Copy link

mdsumner commented Mar 26, 2024

it's now fixed

OSGeo/gdal#9528

this led me to trying a few things, and realizing some stuff - one part is some grid resolving tasks are not purely by setting the crs, so a rotated pole can't be expressed as crs wkt (iiuc), when that works it's because the gdal netcdf driver knows to look for the grid mapping params

this mean i need to review a few things and summarize with a few examples

(also we can't have geolocation arrays that are projected, not that anyone ever would but I'd kind of thought that was possible in principle, but if you try that it triggers "more normal" registration logic, not the warper)

probably not helpful but I'm hoping to illustrate a few cases, and in case it triggers discussions 🙏

@anton-seaice
Copy link
Contributor

it's now fixed

OSGeo/gdal#9528

Thankyou!

this led me to trying a few things, and realizing some stuff - one part is some grid resolving tasks are not purely by setting the crs, so a rotated pole can't be expressed as crs wkt (iiuc), when that works it's because the gdal netcdf driver knows to look for the grid mapping params

Yes. If we could describe the tripole using a crs_wkt then we skip the whole geolocation array for netcdf and life would be easier!

(also we can't have geolocation arrays that are projected, not that anyone ever would but I'd kind of thought that was possible in principle, but if you try that it triggers "more normal" registration logic, not the warper)

This inability to have projected geolocation arrays is slightly counter-intuitive. But I guess there just isn't a use case for it.

probably not helpful but I'm hoping to illustrate a few cases, and in case it triggers discussions 🙏

Useful as always :). Drop past next time you are at IMAS if you like.

@mdsumner
Copy link

mdsumner commented Apr 13, 2024

This is a fun one, apparently two projections in one - I wonder if tripolar is somehow a longlat coord array expression of this?

https://polar.ncep.noaa.gov/global/about/

https://lists.osgeo.org/pipermail/gdal-dev/2024-April/058827.html

@mdsumner
Copy link

it's not obvious, there's still stretchy in the south when it's Mercator ... but maybe that's applied after the fact, in the north it's not obvious either, I though it could be transverse mercator - but maybe it's a rotated pole? Time for me to look at the grid design/history, and maybe that's been pointed to above or in related discussions?.

I'll see if these wild speculations do apply to HYCOM.

I'm throwing some guesses around, because if the two parts of the grid were expressible in trad GIS regular form, it would give a fairly straightforward way to deal with them (in auto-reprojecting software at least). But, even if this kind of scheme is the original design, it seems like there's been some tweaks along the way that can't be parameterized. (We still can wrap up user-friendly approaches though, as per #328).

@anton-seaice
Copy link
Contributor

That looks really similar to the ACCESS-OM grids. In the Mercator part, sometimes the grids have non-uniform latitude spacing (i.e. maybe in HYCOM south of 60S?) which makes it hard I think? Also, I've never found a PROJ / crs code for the two north poles?

@anton-seaice
Copy link
Contributor

Metadata implemented via

COSIMA/esmgrids#6

We might demonstrate a use case:

COSIMA/cosima-recipes#328

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants