[gdal-dev] NetCDF rotated grid support
Rousseau Lambert2, Louis-Philippe (EC)
louis-philippe.rousseaulambert2 at canada.ca
Thu Apr 23 09:14:47 PDT 2020
Hi Even,
I can confirm I also find that working with those projections is a nightmare ;)
I did some more tests with the rotated_pole.nc in gdal GitHub repo (https://github.com/OSGeo/gdal/tree/master/autotest/gdrivers/data) just to make sure the issue was not with my data.
I found that I also had the same issue with rotated_pole.nc when trying to display the file in QGIS using newer version of gdal (3.0.4) and Proj (6.3.1). The NetCDF which should be on Europe, is misplaced and centered on Africa (using 0,0 as origin I guess).
But, I tried to open the same file (rotated_pole.nc) in QGIS using gdal (2.4.1) and proj (5.2.0) and the file was correctly placed, thus using those versions the rotated pole grid is correctly supported/read. The issue I'm having with rotated grid seems to be only in recent version of gdal/proj.
And if I look at the projection reported int the gdalinfo I get:
* gdal 3.0.4 and proj 6.3.0:
* Driver: netCDF/Network Common Data Format Files: rotated_pole.nc Size is 137, 108 Coordinate System is: PROJCRS["unnamed", BASEGEOGCRS["unknown", DATUM["unnamed", ELLIPSOID["Spheroid",6367470,594.313048347956, LENGTHUNIT["metre",1, ID["EPSG",9001]]]], PRIMEM["Greenwich",0, ANGLEUNIT["degree",0.0174532925199433, ID["EPSG",9122]]]], CONVERSION["unnamed", METHOD["Rotated_pole"]], CS[Cartesian,2], AXIS["easting",east, ORDER[1], LENGTHUNIT["metre",1, ID["EPSG",9001]]], AXIS["northing",north, ORDER[2], LENGTHUNIT["metre",1, ID["EPSG",9001]]]] Data axis to CRS axis mapping: 1,2 PROJ.4 string is: '+proj=ob_tran +o_proj=longlat +lon_0=18 +o_lon_p=0 +o_lat_p=39.25 +a=6367470 +b=6367470 +to_meter=0.0174532925199 +wktext' Origin = (-35.470000000560987,23.650000304819269) Pixel Size = (0.440000001121970,-0.439999999286972) Metadata: NC_GLOBAL#Conventions=CF-1.4 projection#false_easting=0 projection#false_northing=0 projection#grid_north_pole_latitude=39.25 projection#grid_north_pole_longitude=-162 projection#north_pole_grid_longitude=0 projection#semi_major_axis=6367470 projection#semi_minor_axis=6356756 tg#grid_mapping=projection ... Corner Coordinates: Upper Left ( -35.4700000, 23.6500003) Lower Left ( -35.4700000, -23.8699996) Upper Right ( 24.8100002, 23.6500003) Lower Right ( 24.8100002, -23.8699996) Center ( -5.3299999, -0.1099997) Band 1 Block=137x1 Type=Int16, ColorInterp=Undefined Computed Min/Max=-3778.000,1476.000
* gdal 2.4.1 and proj 5.2.0:
* Driver: netCDF/Network Common Data Format Files: rotated_pole.nc rotated_pole.nc.aux.xml Size is 137, 108 Coordinate System is: PROJCS["unnamed", GEOGCS["unknown", DATUM["unknown", SPHEROID["Spheroid",6367470,594.3130483479559]], PRIMEM["Greenwich",0], UNIT["degree",0.0174532925199433]], PROJECTION["Rotated_pole"], EXTENSION["PROJ4","+proj=ob_tran +o_proj=longlat +lon_0=18 +o_lon_p=0 +o_lat_p=39.25 +a=6367470 +b=6367470 +to_meter=0.0174532925199 +wktext"]] PROJ.4 string is: '+proj=ob_tran +o_proj=longlat +lon_0=18 +o_lon_p=0 +o_lat_p=39.25 +a=6367470 +b=6367470 +to_meter=0.0174532925199 +wktext' Origin = (-35.470000000560987,23.650000304819269) Pixel Size = (0.440000001121970,-0.439999999286972) Metadata: NC_GLOBAL#Conventions=CF-1.4 projection#false_easting=0 projection#false_northing=0 projection#grid_north_pole_latitude=39.25 projection#grid_north_pole_longitude=-162 projection#north_pole_grid_longitude=0 projection#semi_major_axis=6367470 projection#semi_minor_axis=6356756 tg#grid_mapping=projection ... Corner Coordinates: Upper Left ( -35.4700000, 23.6500003) ( 55d 6'46.50"W, 56d15'18.83"N) Lower Left ( -35.4700000, -23.8699996) ( 16d 4'19.39"W, 18d42'19.70"N) Upper Right ( 24.8100002, 23.6500003) ( 78d43'49.95"E, 63d51'22.90"N) Lower Right ( 24.8100002, -23.8699996) ( 42d35'19.21"E, 22d45'11.78"N) Center ( -5.3299999, -0.1099997) ( 9d37'52.99"E, 50d20'18.47"N) Band 1 Block=137x1 Type=Int16, ColorInterp=Undefined Min=-3778.000 Max=1476.000 Computed Min/Max=-3778.000,1476.000
With the new versions, the EXTENSION is missing from the WKT, which I guess makes the reprojection fail when I try to view it in Qgis ?
Thanks
LP
On 2020-04-15 10:13, Even Rouault wrote:
Louis-Philippe,
Support for rotated grid is a nightmare due to different conventions used for GRIB, netCDF or PROJ ob_tran method. The GDAL GRIB and netCDF drivers normally do what is needed map from the native product convention to the PROJ one.
Below 2 emails I wrote some time ago the OGC CRS SWG group which might bring some light (or deep obscurity!) to this topic:
1)
Rotated pole/grid
From: Even Rouault <even.rouault at spatialys.com><mailto:even.rouault at spatialys.com>
To: "crs.swg at lists.opengeospatial.org"<mailto:crs.swg at lists.opengeospatial.org> <crs.swg at lists.opengeospatial.org><mailto:crs.swg at lists.opengeospatial.org>
Date: 03/10/2019 14:26
Hi again,
(was initially a part of my previous email about "Expressing a ProjectedCRS of
a DerivedGeographicCRS", but this deserves its own thread)
Standardization of rotated pole is another interesting/annoying topic I've
discussed with a few people privately. This tends to be tricky as I always
stumble on different conventions regarding the semantics of parameters and the
maths behind.
For posterity, and my own recollection, here are my latest findings:
1) PROJ's ob_tran (https://proj.org/operations/projections/ob_tran.html)
formulas are a straightforward implementation from Snyder's "Map projections -
a working manual", in particular equations (5-7) and (5-8b) for the forward
path, at page 31 of https://pubs.usgs.gov/pp/1395/report.pdf
(although I've some disagreement with the 'new pole' terminology currently
used in the above HTML help page:
https://github.com/OSGeo/PROJ/pull/1653 )
2) But they are different from the conventions used for PDS3 or ISIS3
planetary image formats, a bit like if the forward and inverse paths had been
switched, so you need to 'switch' parameters, do crazy things like 180 -
pole_latitude, etc when crossing different conventions... Interestingly one
doc for PDS3 mentions at the middle of page 10 of https://pds-imaging.jpl.nasa.gov/documentation/Cassini_BIDRSIS.PDF that "The derivation
of these equations is given in reference [12], p. 29—32, but a
critical pair of signs appear to be exchanged between the formulae
for the forward and inverse transformations. Reference [13], p. 6,
gives the correct forward transformation, and reference [14], pp. 77—
78, give both the forward and inverse transformations"
"reference [12] p29-32" is Snyder's "Map projections - a working manual" I
mentionned before. So this somewhat acknowledges the existence of different
conventions (not sure if there are really "wrong" and "correct" ones)
reference [13] is "Bugayevskiy, L. M., and Snyder, J. P., 1995, Map
projections: A reference manual, Taylor & Francis, London and Bristol,
Pennsylvania, 328 pp."
and [14] "Yang, Q. H., Snyder, J. P., and Tobler, W. R., 2000, Map projection
transformations: Principles and applications, Taylor & Francis, London and
Bristol, Pennsylvania, 367 pp"
PDS/ISIS3 implementation of [13] and [14] is in
https://github.com/USGS-Astrogeology/ISIS3/blob/3.8.0/isis/src/base/objs/
ObliqueCylindrical/ObliqueCylindrical.cpp#L311
3) When dealing with netCDF rotated pole, I've also seen issues with
conventions.
An implementation of that convention is in netcdf-java:
https://github.com/Unidata/netcdf-java/blob/
3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/
geoloc/projection/RotatedPole.java
4) GRIB2 also uses a yet another different convention (with south pole
coordinates as parameters)
An implementation of that convention is also in netcdf-java in
https://github.com/Unidata/netcdf-java/blob/
3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/
geoloc/projection/RotatedLatLon.java
I was also pointed by a data producer of GRIB2 rotated pole datasets that they
used https://www.mathworks.com/matlabcentral/fileexchange/43435-rotated-grid-transform?focused=3795951&tab=function&ue&nocookie=true as the inspiration for
their maths, and luckily it gives consistent result with the Java
RotatedLatLon code.
All in all, a lot of confusion which I think depends if you consider north
pole vs south pole, or the coordinates of the unrotated pole in the rotated
grid, or the coordinates of the new rotated pole in the unrotated grid...
As far as I experimented, there's always a way of using a given implementation
(in my case ob_tran) with the other conventions by tweaking the values of the
3 angular parameters. So, despite the diversity of conventions, considering
standardizing one could still be useful.
Even
2)
Re: Rotated pole/grid
From: Even Rouault <even.rouault at spatialys.com><mailto:even.rouault at spatialys.com>
To: "crs.swg at lists.opengeospatial.org"<mailto:crs.swg at lists.opengeospatial.org> <crs.swg at lists.opengeospatial.org><mailto:crs.swg at lists.opengeospatial.org>
Date: 22/11/2019 02:35
Hi,
reviving this not-so-exciting topic :-)...
For the purpose of dealing with GRIB rotated long/lat grids,
https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml,
I'm about to use a "Pole rotation (GRIB convention)" conversion method with
the following 3 parameters, whose naming and semantics comes mostly from the above GRIB spec:
- "Latitude of the southern pole (GRIB convention)"
Latitude of the point from the unrotated
CRS, expressed in the unrotated CRS, that will become the south pole of the
rotated CRS.
- "Longitude of the southern pole (GRIB convention)"
Longitude of the point from the unrotated
CRS, expressed in the unrotated CRS, that will become the south pole of the
rotated CRS.
- "Axis rotation (GRIB convention)"
The angle of rotation about the new polar
axis (measured clockwise when looking from the southern to the northern pole)
of the coordinate system, assuming the new axis to have been obtained by
first rotating the sphere through southPoleLongInUnrotatedCRS degrees about
the geographic polar axis and then rotating through
(90 + southPoleLatInUnrotatedCRS) degrees so that the southern pole moved
along the (previously rotated) Greenwich meridian.
The explanation of the axis rotation parameter is rather obscure to me. Would need
some scetch... Anyway, in practice, the few datasets I've accessed to
(like the GRIB2 ones at http://collaboration.cmc.ec.gc.ca/cmc/cmos/sample_grib/ or
the GRIB1 one at https://trac.osgeo.org/gdal/ticket/7104 ) use axis rotation = 0
I explicitly chose the " (GRIB convention)" suffix for parameter names as well,
as I'm pretty sure that the semantics of similarly named parameters
for other formulations of Pole rotation could be different.
So an example of a resulting DerivedGeographicCRS:
GEOGCRS["Coordinate System imported from GRIB file",
BASEGEOGCRS["Coordinate System imported from GRIB file",
DATUM["unnamed",
ELLIPSOID["Sphere",6371229,0,
LENGTHUNIT["metre",1,
ID["EPSG",9001]]]],
PRIMEM["Greenwich",0,
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]]],
DERIVINGCONVERSION["Pole rotation (GRIB convention)",
METHOD["Pole rotation (GRIB convention)"],
PARAMETER["Latitude of the southern pole (GRIB convention)",-31.758312,
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]],
PARAMETER["Longitude of the southern pole (GRIB convention)",-92.402969,
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]],
PARAMETER["Axis rotation (GRIB convention)",0,
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]]],
CS[ellipsoidal,2],
AXIS["latitude",north,
ORDER[1],
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]],
AXIS["longitude",east,
ORDER[2],
ANGLEUNIT["degree",0.0174532925199433,
ID["EPSG",9122]]]]
And the maths are below:
{{{
#include <math.h>
#include <stdio.h>
#define PI 3.141592653589793
// Implementation of https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedLatLon.java
void rotate(double lon, double lat, double rot1, double rot2, double cosDlat, double s, double& rlon, double &rlat)
{
double e = (lon - rot1) / 180 * PI; // east
double n = lat / 180 * PI; // north
double cn = cos(n);
double x = cn * cos(e);
double y = cn * sin(e);
double z = sin(n);
double x2 = cosDlat * x + s * z;
double z2 = -s * x + cosDlat * z;
double R = sqrt(x2 * x2 + y * y);
double e2 = atan2(y, x2);
double n2 = atan2(z2, R);
rlon = e2 / PI * 180 - rot2;
rlat = n2 / PI * 180;
}
// Unrotated -> rotated
void forward_transform(double lon, double lat, // Input
double south_pole_lat,
double south_pole_lon,
double axis_rotation,
double& rlon, double& rlat // Output
)
{
double dlat_rad = (south_pole_lat - (-90)) / 180 * PI;
double sinDlat = sin(dlat_rad);
double cosDlat = cos(dlat_rad);
rotate(lon, lat, south_pole_lon, axis_rotation, cosDlat, sinDlat, rlon, rlat);
}
int main()
{
double south_pole_lat = -39.3;
double south_pole_lon = 18;
double axis_rotation = 45;
double lon = 12;
double lat = 55;
double rlon, rlat;
forward_transform(lon, lat,
south_pole_lat, south_pole_lon, axis_rotation,
rlon, rlat);
printf("%.8f %8f\n", rlon, rlat); // -48.44759099 4.439720
// Equivalent to +proj=ob_tran +o_proj=longlat +o_lon_p=-axis_rotation +o_lat_p=-south_pole_lat +lon_0=south_pole_lon
//
//$ echo "12 55" | cs2cs -d 8 +proj=longlat +to +proj=ob_tran +o_proj=longlat +o_lon_p=-45 +o_lat_p=39.3 +lon_0=18
// -48.44759099 4.43972046 0.00000000
}
}}}
Even
--
Spatialys - Geospatial professional services
http://www.spatialys.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osgeo.org/pipermail/gdal-dev/attachments/20200423/25f45852/attachment-0001.html>
More information about the gdal-dev
mailing list