aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/proj/coordinateoperation.hpp11
-rw-r--r--include/proj/internal/coordinateoperation_constants.hpp19
-rw-r--r--scripts/reference_exported_symbols.txt4
-rw-r--r--src/iso19111/c_api.cpp90
-rw-r--r--src/iso19111/coordinateoperation.cpp85
-rw-r--r--src/iso19111/crs.cpp6
-rw-r--r--src/proj_constants.h11
-rw-r--r--src/proj_experimental.h17
-rw-r--r--test/unit/test_c_api.cpp91
9 files changed, 333 insertions, 1 deletions
diff --git a/include/proj/coordinateoperation.hpp b/include/proj/coordinateoperation.hpp
index 1ced5333..e3e3ece3 100644
--- a/include/proj/coordinateoperation.hpp
+++ b/include/proj/coordinateoperation.hpp
@@ -607,6 +607,11 @@ class PROJ_GCC_DLL SingleOperation : virtual public CoordinateOperation {
const common::UnitOfMeasure &targetUnit) const
noexcept;
+ PROJ_INTERNAL double
+ parameterValueNumeric(const char *param_name,
+ const common::UnitOfMeasure &targetUnit) const
+ noexcept;
+
PROJ_INTERNAL double parameterValueNumericAsSI(int epsg_code) const
noexcept;
@@ -1319,6 +1324,12 @@ class PROJ_GCC_DLL Conversion : public SingleOperation {
const common::Length &falseEasting,
const common::Length &falseNorthing);
+ PROJ_DLL static ConversionNNPtr createPoleRotationGRIBConvention(
+ const util::PropertyMap &properties,
+ const common::Angle &southPoleLatInUnrotatedCRS,
+ const common::Angle &southPoleLongInUnrotatedCRS,
+ const common::Angle &axisRotation);
+
PROJ_DLL static ConversionNNPtr
createChangeVerticalUnit(const util::PropertyMap &properties,
const common::Scale &factor);
diff --git a/include/proj/internal/coordinateoperation_constants.hpp b/include/proj/internal/coordinateoperation_constants.hpp
index eb0bb8c5..533599a0 100644
--- a/include/proj/internal/coordinateoperation_constants.hpp
+++ b/include/proj/internal/coordinateoperation_constants.hpp
@@ -1150,6 +1150,22 @@ static const ParamMapping paramVerticalOffsetFile = {
static const ParamMapping *const paramsVERTCON[] = {&paramVerticalOffsetFile,
nullptr};
+static const ParamMapping paramSouthPoleLatGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping paramSouthPoleLonGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping paramAxisRotationGRIB = {
+ PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION, 0, nullptr,
+ common::UnitOfMeasure::Type::ANGULAR, nullptr};
+
+static const ParamMapping *const paramsPoleRotationGRIBConvention[] = {
+ &paramSouthPoleLatGRIB, &paramSouthPoleLonGRIB, &paramAxisRotationGRIB,
+ nullptr};
+
static const MethodMapping otherMethodMappings[] = {
{EPSG_NAME_METHOD_CHANGE_VERTICAL_UNIT,
EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT, nullptr, nullptr, nullptr,
@@ -1172,6 +1188,9 @@ static const MethodMapping otherMethodMappings[] = {
EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION, nullptr, nullptr,
nullptr, paramsAffineParametricTransformation},
+ {PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION, 0, nullptr, nullptr,
+ nullptr, paramsPoleRotationGRIBConvention},
+
{EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC,
EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC, nullptr, nullptr,
nullptr, paramsHelmert3},
diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt
index 4a1f6b90..e20f29b3 100644
--- a/scripts/reference_exported_symbols.txt
+++ b/scripts/reference_exported_symbols.txt
@@ -522,6 +522,7 @@ osgeo::proj::operation::Conversion::create(osgeo::proj::util::PropertyMap const&
osgeo::proj::operation::Conversion::create(osgeo::proj::util::PropertyMap const&, osgeo::proj::util::PropertyMap const&, std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::OperationParameter> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::OperationParameter> > > > const&, std::vector<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::ParameterValue> >, std::allocator<dropbox::oxygen::nn<std::shared_ptr<osgeo::proj::operation::ParameterValue> > > > 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::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&)
@@ -861,6 +862,7 @@ proj_create_conversion_oblique_stereographic
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_popular_visualisation_pseudo_mercator
proj_create_conversion_quadrilateralized_spherical_cube
proj_create_conversion_robinson
@@ -884,6 +886,7 @@ proj_create_conversion_wagner_vii
proj_create_crs_to_crs
proj_create_crs_to_crs_from_pj
proj_create_cs
+proj_create_derived_geographic_crs
proj_create_ellipsoidal_2D_cs
proj_create_ellipsoidal_3D_cs
proj_create_engineering_crs
@@ -955,6 +958,7 @@ proj_init_info
proj_int_list_destroy
proj_is_crs
proj_is_deprecated
+proj_is_derived_crs
proj_is_equivalent_to
proj_list_angular_units
proj_list_destroy
diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp
index 18794f9d..a389e792 100644
--- a/src/iso19111/c_api.cpp
+++ b/src/iso19111/c_api.cpp
@@ -2947,6 +2947,66 @@ PJ *proj_create_geocentric_crs_from_datum(PJ_CONTEXT *ctx, const char *crs_name,
// ---------------------------------------------------------------------------
+/** \brief Create a DerivedGeograhicCRS.
+ *
+ * The returned object must be unreferenced with proj_destroy() after
+ * use.
+ * It should be used by at most one thread at a time.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs_name Name of the GeographicCRS. Or NULL
+ * @param base_geographic_crs Base Geographic CRS. Must not be NULL.
+ * @param conversion Conversion from the base Geographic to the
+ * DerivedGeograhicCRS. Must not be NULL.
+ * @param ellipsoidal_cs Coordinate system. Must not be NULL.
+ *
+ * @return Object of type GeodeticCRS that must be unreferenced with
+ * proj_destroy(), or NULL in case of error.
+ *
+ * @since 7.0
+ */
+PJ *proj_create_derived_geographic_crs(PJ_CONTEXT *ctx, const char *crs_name,
+ const PJ *base_geographic_crs,
+ const PJ *conversion,
+ const PJ *ellipsoidal_cs) {
+ SANITIZE_CTX(ctx);
+ auto base_crs =
+ std::dynamic_pointer_cast<GeographicCRS>(base_geographic_crs->iso_obj);
+ auto conversion_cpp =
+ std::dynamic_pointer_cast<Conversion>(conversion->iso_obj);
+ auto cs = std::dynamic_pointer_cast<EllipsoidalCS>(ellipsoidal_cs->iso_obj);
+ if (!base_crs || !conversion_cpp || !cs) {
+ return nullptr;
+ }
+ try {
+ auto derivedCRS = DerivedGeographicCRS::create(
+ createPropertyMapName(crs_name), NN_NO_CHECK(base_crs),
+ NN_NO_CHECK(conversion_cpp), NN_NO_CHECK(cs));
+ return pj_obj_create(ctx, derivedCRS);
+ } catch (const std::exception &e) {
+ proj_log_error(ctx, __FUNCTION__, e.what());
+ }
+ return nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
+/** \brief Return whether a CRS is a Derived CRS.
+ *
+ * @param ctx PROJ context, or NULL for default context
+ * @param crs CRS. Must not be NULL.
+ *
+ * @return whether a CRS is a Derived CRS.
+ *
+ * @since 7.0
+ */
+int proj_is_derived_crs(PJ_CONTEXT *ctx, const PJ *crs) {
+ SANITIZE_CTX(ctx);
+ return dynamic_cast<DerivedCRS *>(crs->iso_obj.get()) != nullptr;
+}
+
+// ---------------------------------------------------------------------------
+
/** \brief Create a VerticalCRS
*
* The returned object must be unreferenced with proj_destroy() after
@@ -6454,6 +6514,36 @@ PJ *proj_create_conversion_vertical_perspective(
return nullptr;
}
+// ---------------------------------------------------------------------------
+
+/** \brief Instantiate a conversion based on the Pole Rotation method, using the
+ * conventions of the GRIB 1 and GRIB 2 data formats.
+ *
+ * See osgeo::proj::operation::Conversion::createPoleRotationGRIBConvention().
+ *
+ * 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_grib_convention(
+ PJ_CONTEXT *ctx, double south_pole_lat_in_unrotated_crs,
+ double south_pole_long_in_unrotated_crs, double axis_rotation,
+ 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::createPoleRotationGRIBConvention(
+ PropertyMap(), Angle(south_pole_lat_in_unrotated_crs, angUnit),
+ Angle(south_pole_long_in_unrotated_crs, angUnit),
+ Angle(axis_rotation, 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/coordinateoperation.cpp b/src/iso19111/coordinateoperation.cpp
index 860b9a01..f8fbca13 100644
--- a/src/iso19111/coordinateoperation.cpp
+++ b/src/iso19111/coordinateoperation.cpp
@@ -296,6 +296,12 @@ const MethodMapping *getMapping(const char *wkt2_name) noexcept {
return &mapping;
}
}
+ for (const auto &mapping : otherMethodMappings) {
+ if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
+ wkt2_name)) {
+ return &mapping;
+ }
+ }
return nullptr;
}
@@ -1697,6 +1703,16 @@ double SingleOperation::parameterValueNumeric(
return 0.0;
}
+double SingleOperation::parameterValueNumeric(
+ const char *param_name, const common::UnitOfMeasure &targetUnit) const
+ noexcept {
+ const auto &val = parameterValue(param_name, 0);
+ if (val && val->type() == ParameterValue::Type::MEASURE) {
+ return val->value().convertToUnit(targetUnit);
+ }
+ return 0.0;
+}
+
//! @endcond
// ---------------------------------------------------------------------------
@@ -4669,6 +4685,58 @@ ConversionNNPtr Conversion::createVerticalPerspective(
// ---------------------------------------------------------------------------
+/** \brief Instantiate a conversion based on the Pole Rotation method, using
+ * the conventions of the GRIB 1 and GRIB 2 data formats.
+ *
+ * Those are mentionned in the Note 2 of
+ * https://www.nco.ncep.noaa.gov/pmb/docs/grib2/grib2_doc/grib2_temp3-1.shtml
+ *
+ * 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=-rotationAngle
+ * +o_lat_p=-southPoleLatInUnrotatedCRS
+ * +lon_0=southPoleLongInUnrotatedCRS
+ * </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/RotatedLatLon.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 southPoleLatInUnrotatedCRS Latitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param southPoleLongInUnrotatedCRS Longitude of the point from the unrotated
+ * CRS, expressed in the unrotated CRS, that will become the south pole of the
+ * rotated CRS.
+ * @param axisRotation 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.
+ * @return a new Conversion.
+ *
+ * @since 7.0
+ */
+ConversionNNPtr Conversion::createPoleRotationGRIBConvention(
+ const util::PropertyMap &properties,
+ const common::Angle &southPoleLatInUnrotatedCRS,
+ const common::Angle &southPoleLongInUnrotatedCRS,
+ const common::Angle &axisRotation) {
+ return create(properties,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION,
+ createParams(southPoleLatInUnrotatedCRS,
+ southPoleLongInUnrotatedCRS, axisRotation));
+}
+
+// ---------------------------------------------------------------------------
+
//! @cond Doxygen_Suppress
static OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
@@ -6069,6 +6137,23 @@ void Conversion::_exportToPROJString(
} else if (starts_with(methodName, "PROJ ")) {
bConversionDone = true;
createPROJExtensionFromCustomProj(this, formatter, false);
+ } else if (ci_equal(methodName,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
+ double southPoleLat = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double southPoleLon = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ double rotation = parameterValueNumeric(
+ PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION,
+ common::UnitOfMeasure::DEGREE);
+ formatter->addStep("ob_tran");
+ formatter->addParam("o_proj", "longlat");
+ formatter->addParam("o_lon_p", -rotation);
+ formatter->addParam("o_lat_p", -southPoleLat);
+ formatter->addParam("lon_0", southPoleLon);
+ bConversionDone = true;
} else if (formatter->convention() ==
io::PROJStringFormatter::Convention::PROJ_5 &&
isZUnitConversion) {
diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp
index cf533bd3..812707ef 100644
--- a/src/iso19111/crs.cpp
+++ b/src/iso19111/crs.cpp
@@ -45,6 +45,8 @@
#include "proj/internal/internal.hpp"
#include "proj/internal/io_internal.hpp"
+#include "proj_constants.h"
+
#include <algorithm>
#include <cassert>
#include <cstring>
@@ -4893,7 +4895,9 @@ void DerivedGeographicCRS::_exportToPROJString(
if (methodName == "PROJ ob_tran o_proj=longlat" ||
methodName == "PROJ ob_tran o_proj=lonlat" ||
methodName == "PROJ ob_tran o_proj=latlong" ||
- methodName == "PROJ ob_tran o_proj=latlon") {
+ methodName == "PROJ ob_tran o_proj=latlon" ||
+ ci_equal(methodName,
+ PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION)) {
l_conv->_exportToPROJString(formatter);
return;
}
diff --git a/src/proj_constants.h b/src/proj_constants.h
index 62cf94be..5c642862 100644
--- a/src/proj_constants.h
+++ b/src/proj_constants.h
@@ -215,6 +215,8 @@
#define EPSG_NAME_METHOD_VERTICAL_PERSPECTIVE "Vertical Perspective"
#define EPSG_CODE_METHOD_VERTICAL_PERSPECTIVE 9838
+#define PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION "Pole rotation (GRIB convention)"
+
/* ------------------------------------------------------------------------ */
/* Projection parameters */
@@ -474,6 +476,15 @@
#define \
EPSG_NAME_PARAMETER_FLATTENING_DIFFERENCE "Flattening difference"
+#define PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LATITUDE_GRIB_CONVENTION \
+ "Latitude of the southern pole (GRIB convention)"
+
+#define PROJ_WKT2_NAME_PARAMETER_SOUTH_POLE_LONGITUDE_GRIB_CONVENTION \
+ "Longitude of the southern pole (GRIB convention)"
+
+#define PROJ_WKT2_NAME_PARAMETER_AXIS_ROTATION_GRIB_CONVENTION \
+ "Axis rotation (GRIB convention)"
+
/* ------------------------------------------------------------------------ */
#define EPSG_CODE_METHOD_NTV1 9614
diff --git a/src/proj_experimental.h b/src/proj_experimental.h
index 0886ba28..50f1fb9f 100644
--- a/src/proj_experimental.h
+++ b/src/proj_experimental.h
@@ -191,6 +191,16 @@ PJ PROJ_DLL *proj_create_geocentric_crs_from_datum(
const char *linear_units,
double linear_units_conv);
+PJ PROJ_DLL *proj_create_derived_geographic_crs(
+ PJ_CONTEXT *ctx,
+ const char *crs_name,
+ const PJ* base_geographic_crs,
+ const PJ* conversion,
+ const PJ* ellipsoidal_cs);
+
+int PROJ_DLL proj_is_derived_crs(PJ_CONTEXT *ctx,
+ const PJ* crs);
+
PJ PROJ_DLL *proj_alter_name(PJ_CONTEXT *ctx,
const PJ* obj, const char* name);
@@ -947,6 +957,13 @@ PJ PROJ_DLL *proj_create_conversion_vertical_perspective(
const char* ang_unit_name, double ang_unit_conv_factor,
const char* linear_unit_name, double linear_unit_conv_factor);
+PJ PROJ_DLL *proj_create_conversion_pole_rotation_grib_convention(
+ PJ_CONTEXT *ctx,
+ double south_pole_lat_in_unrotated_crs,
+ double south_pole_long_in_unrotated_crs,
+ double axis_rotation,
+ 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 d8816acf..f87f6589 100644
--- a/test/unit/test_c_api.cpp
+++ b/test/unit/test_c_api.cpp
@@ -2411,6 +2411,13 @@ TEST_F(CApi, proj_create_projections) {
ObjectKeeper keeper_projCRS(projCRS);
ASSERT_NE(projCRS, nullptr);
}
+ {
+ auto projCRS = proj_create_conversion_pole_rotation_grib_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*/
}
@@ -4343,4 +4350,88 @@ TEST_F(CApi, proj_create_vertical_crs_ex_with_geog_crs) {
EXPECT_EQ(std::string(proj_5_bis), std::string(proj_5));
}
+// ---------------------------------------------------------------------------
+
+TEST_F(CApi, proj_create_derived_geographic_crs) {
+
+ PJ *crs_4326 = proj_create(m_ctxt, "EPSG:4326");
+ ObjectKeeper keeper_crs_4326(crs_4326);
+ ASSERT_NE(crs_4326, nullptr);
+
+ PJ *conversion = proj_create_conversion_pole_rotation_grib_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_4326);
+ ObjectKeeper keeper_cs(cs);
+ ASSERT_NE(cs, nullptr);
+
+ ASSERT_EQ(
+ proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS",
+ conversion, // wrong type of object
+ conversion, cs),
+ nullptr);
+
+ ASSERT_EQ(
+ proj_create_derived_geographic_crs(m_ctxt, "my rotated CRS", crs_4326,
+ crs_4326, // wrong type of object
+ cs),
+ nullptr);
+
+ ASSERT_EQ(proj_create_derived_geographic_crs(
+ m_ctxt, "my rotated CRS", crs_4326, conversion,
+ conversion // wrong type of object
+ ),
+ nullptr);
+
+ PJ *derived_crs = proj_create_derived_geographic_crs(
+ m_ctxt, "my rotated CRS", crs_4326, conversion, cs);
+ ObjectKeeper keeper_derived_crs(derived_crs);
+ ASSERT_NE(derived_crs, nullptr);
+
+ EXPECT_FALSE(proj_is_derived_crs(m_ctxt, crs_4326));
+ EXPECT_TRUE(proj_is_derived_crs(m_ctxt, derived_crs));
+
+ auto wkt = proj_as_wkt(m_ctxt, derived_crs, PJ_WKT2_2019, nullptr);
+ const char *expected_wkt =
+ "GEOGCRS[\"my rotated CRS\",\n"
+ " BASEGEOGCRS[\"WGS 84\",\n"
+ " DATUM[\"World Geodetic System 1984\",\n"
+ " ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n"
+ " LENGTHUNIT[\"metre\",1]]],\n"
+ " PRIMEM[\"Greenwich\",0,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433]]],\n"
+ " DERIVINGCONVERSION[\"Pole rotation (GRIB convention)\",\n"
+ " METHOD[\"Pole rotation (GRIB convention)\"],\n"
+ " PARAMETER[\"Latitude of the southern pole (GRIB "
+ "convention)\",2,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Longitude of the southern pole (GRIB "
+ "convention)\",3,\n"
+ " ANGLEUNIT[\"degree\",0.0174532925199433,\n"
+ " ID[\"EPSG\",9122]]],\n"
+ " PARAMETER[\"Axis rotation (GRIB 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=3 +datum=WGS84 +no_defs "
+ "+type=crs"));
+}
} // namespace