diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2021-09-02 09:19:22 +0200 |
|---|---|---|
| committer | Even Rouault <even.rouault@spatialys.com> | 2021-09-02 09:19:22 +0200 |
| commit | f19b2948efcb7433d41e51d5128c5baa68261599 (patch) | |
| tree | bc057566a951b749455c3a32e8b67052ba5a2b62 | |
| parent | 1b180864413fa98aed9d79868eec8ceb689421a9 (diff) | |
| download | PROJ-f19b2948efcb7433d41e51d5128c5baa68261599.tar.gz PROJ-f19b2948efcb7433d41e51d5128c5baa68261599.zip | |
Add proj_create_conversion_pole_rotation_netcdf_cf_convention() to address netCDF datasets using a pole rotation method
| -rw-r--r-- | include/proj/coordinateoperation.hpp | 6 | ||||
| -rw-r--r-- | scripts/reference_exported_symbols.txt | 2 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 31 | ||||
| -rw-r--r-- | src/iso19111/crs.cpp | 4 | ||||
| -rw-r--r-- | src/iso19111/operation/conversion.cpp | 63 | ||||
| -rw-r--r-- | src/iso19111/operation/parammappings.cpp | 19 | ||||
| -rw-r--r-- | src/proj_constants.h | 11 | ||||
| -rw-r--r-- | src/proj_experimental.h | 8 | ||||
| -rw-r--r-- | test/unit/test_c_api.cpp | 73 |
9 files changed, 215 insertions, 2 deletions
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp index aeef04f3..3d2ab800 100644 --- a/include/proj/coordinateoperation.hpp +++ b/include/proj/coordinateoperation.hpp @@ -1340,6 +1340,12 @@ class PROJ_GCC_DLL Conversion : public SingleOperation { const common::Angle &southPoleLongInUnrotatedCRS, const common::Angle &axisRotation); + PROJ_DLL static ConversionNNPtr createPoleRotationNetCDFCFConvention( + const util::PropertyMap &properties, + const common::Angle &gridNorthPoleLatitude, + const common::Angle &gridNorthPoleLongitude, + const common::Angle &northPoleGridLongitude); + PROJ_DLL static ConversionNNPtr createChangeVerticalUnit(const util::PropertyMap &properties, const common::Scale &factor); diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index 9b84c09b..d49a59b2 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -571,6 +571,7 @@ osgeo::proj::operation::Conversion::create(osgeo::proj::util::PropertyMap const& osgeo::proj::operation::Conversion::createPolarStereographicVariantA(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Scale const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createPolarStereographicVariantB(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createPoleRotationGRIBConvention(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&) +osgeo::proj::operation::Conversion::createPoleRotationNetCDFCFConvention(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&) osgeo::proj::operation::Conversion::createPopularVisualisationPseudoMercator(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createQuadrilateralizedSphericalCube(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) osgeo::proj::operation::Conversion::createRobinson(osgeo::proj::util::PropertyMap const&, osgeo::proj::common::Angle const&, osgeo::proj::common::Length const&, osgeo::proj::common::Length const&) @@ -884,6 +885,7 @@ proj_create_conversion_orthographic proj_create_conversion_polar_stereographic_variant_a proj_create_conversion_polar_stereographic_variant_b proj_create_conversion_pole_rotation_grib_convention +proj_create_conversion_pole_rotation_netcdf_cf_convention proj_create_conversion_popular_visualisation_pseudo_mercator proj_create_conversion_quadrilateralized_spherical_cube proj_create_conversion_robinson diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index ace699e4..1bf6cbf0 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -7085,6 +7085,37 @@ PJ *proj_create_conversion_pole_rotation_grib_convention( return nullptr; } +// --------------------------------------------------------------------------- + +/** \brief Instantiate a conversion based on the Pole Rotation method, using + * the conventions of the netCDF CF convention for the netCDF format. + * + * See + * osgeo::proj::operation::Conversion::createPoleRotationNetCDFCFConvention(). + * + * Linear parameters are expressed in (linear_unit_name, + * linear_unit_conv_factor). + * Angular parameters are expressed in (ang_unit_name, ang_unit_conv_factor). + */ +PJ *proj_create_conversion_pole_rotation_netcdf_cf_convention( + PJ_CONTEXT *ctx, double grid_north_pole_latitude, + double grid_north_pole_longitude, double north_pole_grid_longitude, + const char *ang_unit_name, double ang_unit_conv_factor) { + SANITIZE_CTX(ctx); + try { + UnitOfMeasure angUnit( + createAngularUnit(ang_unit_name, ang_unit_conv_factor)); + auto conv = Conversion::createPoleRotationNetCDFCFConvention( + PropertyMap(), Angle(grid_north_pole_latitude, angUnit), + Angle(grid_north_pole_longitude, angUnit), + Angle(north_pole_grid_longitude, angUnit)); + return proj_create_conversion(ctx, conv); + } catch (const std::exception &e) { + proj_log_error(ctx, __FUNCTION__, e.what()); + } + return nullptr; +} + /* END: Generated by scripts/create_c_api_projections.py*/ // --------------------------------------------------------------------------- diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 3e825ff0..a15758ab 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -5983,7 +5983,9 @@ void DerivedGeographicCRS::_exportToPROJString( } if (ci_equal(methodName, - PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) { + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION) || + ci_equal(methodName, + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) { l_conv->_exportToPROJString(formatter); return; } diff --git a/src/iso19111/operation/conversion.cpp b/src/iso19111/operation/conversion.cpp index e884db3c..3cc452f8 100644 --- a/src/iso19111/operation/conversion.cpp +++ b/src/iso19111/operation/conversion.cpp @@ -2227,6 +2227,51 @@ ConversionNNPtr Conversion::createPoleRotationGRIBConvention( // --------------------------------------------------------------------------- +/** \brief Instantiate a conversion based on the Pole Rotation method, using + * the conventions of the netCDF CF convention for the netCDF format. + * + * Those are mentioned in the Note 2 of + * https://cfconventions.org/Data/cf-conventions/cf-conventions-1.8/cf-conventions.html#_rotated_pole + * + * Several conventions for the pole rotation method exists. + * The parameters provided in this method are remapped to the PROJ ob_tran + * operation with: + * <pre> + * +proj=ob_tran +o_proj=longlat +o_lon_p=northPoleGridLongitude + * +o_lat_p=gridNorthPoleLatitude + * +lon_0=180+gridNorthPoleLongitude + * </pre> + * + * Another implementation of that convention is also in the netcdf-java library: + * https://github.com/Unidata/netcdf-java/blob/3ce72c0cd167609ed8c69152bb4a004d1daa9273/cdm/core/src/main/java/ucar/unidata/geoloc/projection/RotatedPole.java + * + * The PROJ implementation of this method assumes a spherical ellipsoid. + * + * @param properties See \ref general_properties of the conversion. If the name + * is not provided, it is automatically set. + * @param gridNorthPoleLatitude True latitude of the north pole of the rotated + * grid + * @param gridNorthPoleLongitude True longitude of the north pole of the rotated + * grid. + * @param northPoleGridLongitude Longitude of the true north pole in the rotated + * grid. + * @return a new Conversion. + * + * @since 8.2 + */ +ConversionNNPtr Conversion::createPoleRotationNetCDFCFConvention( + const util::PropertyMap &properties, + const common::Angle &gridNorthPoleLatitude, + const common::Angle &gridNorthPoleLongitude, + const common::Angle &northPoleGridLongitude) { + return create(properties, + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION, + createParams(gridNorthPoleLatitude, gridNorthPoleLongitude, + northPoleGridLongitude)); +} + +// --------------------------------------------------------------------------- + /** \brief Instantiate a conversion based on the Change of Vertical Unit * method. * @@ -3628,6 +3673,24 @@ void Conversion::_exportToPROJString( formatter->addParam("o_lat_p", -southPoleLat); formatter->addParam("lon_0", southPoleLon); bConversionDone = true; + } else if (ci_equal( + methodName, + PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) { + double gridNorthPoleLatitude = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION, + common::UnitOfMeasure::DEGREE); + double gridNorthPoleLongitude = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION, + common::UnitOfMeasure::DEGREE); + double northPoleGridLongitude = parameterValueNumeric( + PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION, + common::UnitOfMeasure::DEGREE); + formatter->addStep("ob_tran"); + formatter->addParam("o_proj", "longlat"); + formatter->addParam("o_lon_p", northPoleGridLongitude); + formatter->addParam("o_lat_p", gridNorthPoleLatitude); + formatter->addParam("lon_0", 180 + gridNorthPoleLongitude); + bConversionDone = true; } else if (ci_equal(methodName, "Adams_Square_II")) { // Look for ESRI method and parameter names (to be opposed // to the OGC WKT2 names we use elsewhere, because there's no mapping diff --git a/src/iso19111/operation/parammappings.cpp b/src/iso19111/operation/parammappings.cpp index 5999d535..9acc9e93 100644 --- a/src/iso19111/operation/parammappings.cpp +++ b/src/iso19111/operation/parammappings.cpp @@ -1305,6 +1305,22 @@ static const ParamMapping *const paramsPoleRotationGRIBConvention[] = { ¶mSouthPoleLatGRIB, ¶mSouthPoleLonGRIB, ¶mAxisRotationGRIB, nullptr}; +static const ParamMapping paramGridNorthPoleLatitudeNetCDF = { + PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION, 0, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramGridNorthPoleLongitudeNetCDF = { + PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION, 0, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping paramNorthPoleGridLongitudeNetCDF = { + PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION, 0, + nullptr, common::UnitOfMeasure::Type::ANGULAR, nullptr}; + +static const ParamMapping *const paramsPoleRotationNetCDFCFConvention[] = { + ¶mGridNorthPoleLatitudeNetCDF, ¶mGridNorthPoleLongitudeNetCDF, + ¶mNorthPoleGridLongitudeNetCDF, nullptr}; + static const MethodMapping otherMethodMappings[] = { {EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT, EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr, @@ -1333,6 +1349,9 @@ static const MethodMapping otherMethodMappings[] = { {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr, nullptr, paramsPoleRotationGRIBConvention}, + {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION, 0, nullptr, + nullptr, nullptr, paramsPoleRotationNetCDFCFConvention}, + {EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr, nullptr, paramsHelmert3}, diff --git a/src/proj_constants.h b/src/proj_constants.h index 7c6d018a..f67a0583 100644 --- a/src/proj_constants.h +++ b/src/proj_constants.h @@ -240,6 +240,8 @@ #define PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION "Pole rotation (GRIB convention)" +#define PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION "Pole rotation (netCDF CF convention)" + #define EPSG_CODE_METHOD_COLOMBIA_URBAN 1052 #define EPSG_NAME_METHOD_COLOMBIA_URBAN "Colombia Urban" @@ -514,6 +516,15 @@ #define PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION \ "Axis rotation (GRIB convention)" +#define PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LATITUDE_NETCDF_CONVENTION \ + "Grid north pole latitude (netCDF CF convention)" + +#define PROJ_WKT2_NAME_PARAMETER_GRID_NORTH_POLE_LONGITUDE_NETCDF_CONVENTION \ + "Grid north pole longitude (netCDF CF convention)" + +#define PROJ_WKT2_NAME_PARAMETER_NORTH_POLE_GRID_LONGITUDE_NETCDF_CONVENTION \ + "North pole grid longitude (netCDF CF convention)" + /* ------------------------------------------------------------------------ */ #define EPSG_CODE_METHOD_NTV1 9614 diff --git a/src/proj_experimental.h b/src/proj_experimental.h index 05e49451..41865f14 100644 --- a/src/proj_experimental.h +++ b/src/proj_experimental.h @@ -964,6 +964,14 @@ PJ PROJ_DLL *proj_create_conversion_pole_rotation_grib_convention( double axis_rotation, const char* ang_unit_name, double ang_unit_conv_factor); +PJ PROJ_DLL *proj_create_conversion_pole_rotation_netcdf_cf_convention( + PJ_CONTEXT *ctx, + double grid_north_pole_latitude, + double grid_north_pole_longitude, + double north_pole_grid_longitude, + const char *ang_unit_name, + double ang_unit_conv_factor); + /* END: Generated by scripts/create_c_api_projections.py*/ /**@}*/ diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index 2a82af64..44dceb97 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -2805,7 +2805,13 @@ TEST_F(CApi, proj_create_projections) { ObjectKeeper keeper_projCRS(projCRS); ASSERT_NE(projCRS, nullptr); } - + { + auto projCRS = + proj_create_conversion_pole_rotation_netcdf_cf_convention( + m_ctxt, 0, 0, 0, "Degree", 0.0174532925199433); + ObjectKeeper keeper_projCRS(projCRS); + ASSERT_NE(projCRS, nullptr); + } /* END: Generated by scripts/create_c_api_projections.py*/ } @@ -5199,6 +5205,71 @@ TEST_F(CApi, proj_create_derived_geographic_crs) { // --------------------------------------------------------------------------- +TEST_F(CApi, proj_create_derived_geographic_crs_netcdf_cf) { + + PJ *crs_4019 = proj_create(m_ctxt, "EPSG:4019"); + ObjectKeeper keeper_crs_4019(crs_4019); + ASSERT_NE(crs_4019, nullptr); + + PJ *conversion = proj_create_conversion_pole_rotation_netcdf_cf_convention( + m_ctxt, 2, 3, 4, "Degree", 0.0174532925199433); + ObjectKeeper keeper_conversion(conversion); + ASSERT_NE(conversion, nullptr); + + PJ *cs = proj_crs_get_coordinate_system(m_ctxt, crs_4019); + ObjectKeeper keeper_cs(cs); + ASSERT_NE(cs, nullptr); + + PJ *derived_crs = proj_create_derived_geographic_crs( + m_ctxt, "my rotated CRS", crs_4019, conversion, cs); + ObjectKeeper keeper_derived_crs(derived_crs); + ASSERT_NE(derived_crs, nullptr); + + auto wkt = proj_as_wkt(m_ctxt, derived_crs, PJ_WKT2_2019, nullptr); + const char *expected_wkt = + "GEOGCRS[\"my rotated CRS\",\n" + " BASEGEOGCRS[\"Unknown datum based upon the GRS 1980 ellipsoid\",\n" + " DATUM[\"Not specified (based on GRS 1980 ellipsoid)\",\n" + " ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n" + " LENGTHUNIT[\"metre\",1]]],\n" + " PRIMEM[\"Greenwich\",0,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n" + " DERIVINGCONVERSION[\"Pole rotation (netCDF CF convention)\",\n" + " METHOD[\"Pole rotation (netCDF CF convention)\"],\n" + " PARAMETER[\"Grid north pole latitude (netCDF CF " + "convention)\",2,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " PARAMETER[\"Grid north pole longitude (netCDF CF " + "convention)\",3,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " PARAMETER[\"North pole grid longitude (netCDF CF " + "convention)\",4,\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]]],\n" + " CS[ellipsoidal,2],\n" + " AXIS[\"geodetic latitude (Lat)\",north,\n" + " ORDER[1],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]],\n" + " AXIS[\"geodetic longitude (Lon)\",east,\n" + " ORDER[2],\n" + " ANGLEUNIT[\"degree\",0.0174532925199433,\n" + " ID[\"EPSG\",9122]]]]"; + + ASSERT_NE(wkt, nullptr); + EXPECT_EQ(wkt, std::string(expected_wkt)); + + auto proj_5 = proj_as_proj_string(m_ctxt, derived_crs, PJ_PROJ_5, nullptr); + ASSERT_NE(proj_5, nullptr); + EXPECT_EQ(proj_5, std::string("+proj=ob_tran +o_proj=longlat +o_lon_p=4 " + "+o_lat_p=2 +lon_0=183 +ellps=GRS80 +no_defs " + "+type=crs")); +} + +// --------------------------------------------------------------------------- + TEST_F(CApi, proj_context_set_sqlite3_vfs_name) { PJ_CONTEXT *ctx = proj_context_create(); |
