diff --git a/include/proj/crs.hpp b/include/proj/crs.hpp index 6d10a21794..57091377ca 100644 --- a/include/proj/crs.hpp +++ b/include/proj/crs.hpp @@ -1273,6 +1273,10 @@ class PROJ_GCC_DLL DerivedProjectedCRS final : public DerivedCRS { const operation::ConversionNNPtr &derivingConversionIn, const cs::CoordinateSystemNNPtr &csIn); + PROJ_DLL DerivedProjectedCRSNNPtr + demoteTo2D(const std::string &newName, + const io::DatabaseContextPtr &dbContext) const; + //! @cond Doxygen_Suppress PROJ_INTERNAL void _exportToWKT(io::WKTFormatter *formatter) const override; // throw(io::FormattingException) diff --git a/scripts/reference_exported_symbols.txt b/scripts/reference_exported_symbols.txt index 0a01f804fc..f1ad00b18e 100644 --- a/scripts/reference_exported_symbols.txt +++ b/scripts/reference_exported_symbols.txt @@ -136,6 +136,7 @@ osgeo::proj::crs::DerivedGeographicCRS::demoteTo2D(std::string const&, std::shar osgeo::proj::crs::DerivedGeographicCRS::~DerivedGeographicCRS() osgeo::proj::crs::DerivedProjectedCRS::baseCRS() const osgeo::proj::crs::DerivedProjectedCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) +osgeo::proj::crs::DerivedProjectedCRS::demoteTo2D(std::string const&, std::shared_ptr const&) const osgeo::proj::crs::DerivedProjectedCRS::~DerivedProjectedCRS() osgeo::proj::crs::DerivedVerticalCRS::baseCRS() const osgeo::proj::crs::DerivedVerticalCRS::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&, dropbox::oxygen::nn > const&) diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 416e8a997f..c1c9e7c6c1 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -1260,6 +1260,23 @@ CRSNNPtr CRS::promoteTo3D(const std::string &newName, } } + else if (auto derivedProjCRS = + dynamic_cast(this)) { + const auto &axisList = derivedProjCRS->coordinateSystem()->axisList(); + if (axisList.size() == 2) { + auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], + axisList[1], + verticalAxisIfNotAlreadyPresent); + auto baseProj3DCRS = util::nn_dynamic_pointer_cast( + derivedProjCRS->baseCRS()->promoteTo3D( + std::string(), dbContext, verticalAxisIfNotAlreadyPresent)); + return util::nn_static_pointer_cast( + DerivedProjectedCRS::create( + createProperties(), NN_CHECK_THROW(baseProj3DCRS), + derivedProjCRS->derivingConversion(), cs)); + } + } + else if (auto geogCRS = dynamic_cast(this)) { const auto &axisList = geogCRS->coordinateSystem()->axisList(); if (axisList.size() == 2) { @@ -1358,6 +1375,11 @@ CRSNNPtr CRS::demoteTo2D(const std::string &newName, return derivedGeogCRS->demoteTo2D(newName, dbContext); } + else if (auto derivedProjCRS = + dynamic_cast(this)) { + return derivedProjCRS->demoteTo2D(newName, dbContext); + } + else if (auto geogCRS = dynamic_cast(this)) { return geogCRS->demoteTo2D(newName, dbContext); } @@ -6493,6 +6515,39 @@ DerivedProjectedCRSNNPtr DerivedProjectedCRS::create( // --------------------------------------------------------------------------- +/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already + * the case. + * + * + * @param newName Name of the new CRS. If empty, nameStr() will be used. + * @param dbContext Database context to look for potentially already registered + * 2D CRS. May be nullptr. + * @return a new CRS demoted to 2D, or the current one if already 2D or not + * applicable. + * @since 9.1.1 + */ +DerivedProjectedCRSNNPtr +DerivedProjectedCRS::demoteTo2D(const std::string &newName, + const io::DatabaseContextPtr &dbContext) const { + + const auto &axisList = coordinateSystem()->axisList(); + if (axisList.size() == 3) { + auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0], + axisList[1]); + auto baseProj2DCRS = util::nn_dynamic_pointer_cast( + baseCRS()->demoteTo2D(std::string(), dbContext)); + return DerivedProjectedCRS::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + !newName.empty() ? newName : nameStr()), + NN_CHECK_THROW(baseProj2DCRS), derivingConversion(), cs); + } + + return NN_NO_CHECK(std::dynamic_pointer_cast( + shared_from_this().as_nullable())); +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp index 32a4ca056e..115f1a9a9a 100644 --- a/test/unit/test_crs.cpp +++ b/test/unit/test_crs.cpp @@ -6791,6 +6791,32 @@ TEST(crs, promoteTo3D_and_demoteTo2D) { EXPECT_TRUE(demoted->demoteTo2D(std::string(), nullptr) ->isEquivalentTo(demoted.get())); } + + { + auto crs = createDerivedProjectedCRS(); + auto crs3D = crs->promoteTo3D(std::string(), dbContext); + auto crs3DAsDerivedProj = + nn_dynamic_pointer_cast(crs3D); + ASSERT_TRUE(crs3DAsDerivedProj != nullptr); + EXPECT_EQ(crs3DAsDerivedProj->baseCRS() + ->coordinateSystem() + ->axisList() + .size(), + 3U); + EXPECT_EQ(crs3DAsDerivedProj->coordinateSystem()->axisList().size(), + 3U); + EXPECT_TRUE(crs3DAsDerivedProj->promoteTo3D(std::string(), nullptr) + ->isEquivalentTo(crs3DAsDerivedProj.get())); + + auto demoted = crs3DAsDerivedProj->demoteTo2D(std::string(), dbContext); + EXPECT_EQ(demoted->baseCRS()->coordinateSystem()->axisList().size(), + 2U); + EXPECT_EQ(demoted->coordinateSystem()->axisList().size(), 2U); + EXPECT_TRUE(demoted->isEquivalentTo( + crs.get(), IComparable::Criterion::EQUIVALENT)); + EXPECT_TRUE(demoted->demoteTo2D(std::string(), nullptr) + ->isEquivalentTo(demoted.get())); + } } // ---------------------------------------------------------------------------