aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/proj/coordinateoperation.hpp6
-rw-r--r--scripts/reference_exported_symbols.txt2
-rw-r--r--src/iso19111/c_api.cpp31
-rw-r--r--src/iso19111/crs.cpp4
-rw-r--r--src/iso19111/operation/conversion.cpp63
-rw-r--r--src/iso19111/operation/parammappings.cpp19
-rw-r--r--src/proj_constants.h11
-rw-r--r--src/proj_experimental.h8
-rw-r--r--test/unit/test_c_api.cpp73
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[] = {
&paramSouthPoleLatGRIB, &paramSouthPoleLonGRIB, &paramAxisRotationGRIB,
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[] = {
+ &paramGridNorthPoleLatitudeNetCDF, &paramGridNorthPoleLongitudeNetCDF,
+ &paramNorthPoleGridLongitudeNetCDF, 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();