diff --git a/src/iso19111/crs.cpp b/src/iso19111/crs.cpp index 2d589ad169..51317c18d0 100644 --- a/src/iso19111/crs.cpp +++ b/src/iso19111/crs.cpp @@ -6506,6 +6506,33 @@ DerivedVerticalCRSNNPtr DerivedVerticalCRS::create( void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const { const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; if (!isWKT2) { + + bool useBaseMethod = true; + const DerivedVerticalCRS *dvcrs = this; + while (true) { + // If the derived vertical CRS is obtained through simple conversion + // methods that just do unit change or height/depth reversal, export + // it as a regular VerticalCRS + const int methodCode = + dvcrs->derivingConversionRef()->method()->getEPSGCode(); + if (methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT || + methodCode == + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR || + methodCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { + dvcrs = dynamic_cast(baseCRS().get()); + if (dvcrs == nullptr) { + break; + } + } else { + useBaseMethod = false; + break; + } + } + if (useBaseMethod) { + VerticalCRS::_exportToWKT(formatter); + return; + } + io::FormattingException::Throw( "DerivedVerticalCRS can only be exported to WKT2"); } diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index e3da80e0ff..6ddeed7a7e 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -6491,12 +6491,13 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]); auto op = factoryOp->createCoordinateOperation(tokensOp[3], true); - std::string name(baseCRS->nameStr()); + const auto &baseName = baseCRS->nameStr(); + std::string name(baseName); auto geogCRS = util::nn_dynamic_pointer_cast(baseCRS); if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3 && - baseCRS->nameStr().find("3D") == std::string::npos) { + baseName.find("3D") == std::string::npos) { name += " (3D)"; } name += " / "; @@ -6532,15 +6533,61 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, baseCRS)) { return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs), convNN, cs); - } else if (dynamic_cast(baseCRS.get()) && - dynamic_cast(cs.get())) { - return DerivedVerticalCRS::create( - props, - NN_NO_CHECK(util::nn_dynamic_pointer_cast( - baseCRS)), - convNN, - NN_NO_CHECK( - util::nn_dynamic_pointer_cast(cs))); + } else if (auto vertBaseCRS = + util::nn_dynamic_pointer_cast( + baseCRS)) { + if (auto vertCS = + util::nn_dynamic_pointer_cast(cs)) { + const int methodCode = convNN->method()->getEPSGCode(); + std::string newName(baseName); + std::string unitNameSuffix; + for (const char *suffix : {" (ft)", " (ftUS)"}) { + if (ends_with(newName, suffix)) { + unitNameSuffix = suffix; + newName.resize(newName.size() - strlen(suffix)); + break; + } + } + bool newNameOk = false; + if (methodCode == + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR || + methodCode == + EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { + const auto &unitName = + vertCS->axisList()[0]->unit().name(); + if (unitName == UnitOfMeasure::METRE.name()) { + newNameOk = true; + } else if (unitName == UnitOfMeasure::FOOT.name()) { + newName += " (ft)"; + newNameOk = true; + } else if (unitName == + UnitOfMeasure::US_FOOT.name()) { + newName += " (ftUS)"; + newNameOk = true; + } + } else if (methodCode == + EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { + if (ends_with(newName, " height")) { + newName.resize(newName.size() - + strlen(" height")); + newName += " depth"; + newName += unitNameSuffix; + newNameOk = true; + } else if (ends_with(newName, " depth")) { + newName.resize(newName.size() - + strlen(" depth")); + newName += " height"; + newName += unitNameSuffix; + newNameOk = true; + } + } + if (newNameOk) { + props.set(IdentifiedObject::NAME_KEY, newName); + } + return DerivedVerticalCRS::create( + props, NN_NO_CHECK(vertBaseCRS), convNN, + NN_NO_CHECK(vertCS)); + } } } diff --git a/test/unit/test_crs.cpp b/test/unit/test_crs.cpp index 719cd125ef..614580f213 100644 --- a/test/unit/test_crs.cpp +++ b/test/unit/test_crs.cpp @@ -5620,6 +5620,32 @@ TEST(crs, DerivedVerticalCRS_WKT1) { // --------------------------------------------------------------------------- +TEST(crs, DerivedVerticalCRS_WKT1_when_simple_derivation) { + + auto derivingConversion = + Conversion::createChangeVerticalUnit(PropertyMap().set( + IdentifiedObject::NAME_KEY, "Vertical Axis Unit Conversion")); + + auto crs = DerivedVerticalCRS::create( + PropertyMap().set(IdentifiedObject::NAME_KEY, "Derived vertCRS"), + createVerticalCRS(), derivingConversion, + VerticalCS::createGravityRelatedHeight(UnitOfMeasure::FOOT)); + + auto expected = "VERT_CS[\"Derived vertCRS\",\n" + " VERT_DATUM[\"Ordnance Datum Newlyn\",2005,\n" + " AUTHORITY[\"EPSG\",\"5101\"]],\n" + " UNIT[\"foot\",0.3048,\n" + " AUTHORITY[\"EPSG\",\"9002\"]],\n" + " AXIS[\"Gravity-related height\",UP]]"; + + EXPECT_EQ( + crs->exportToWKT( + WKTFormatter::create(WKTFormatter::Convention::WKT1_GDAL).get()), + expected); +} + +// --------------------------------------------------------------------------- + static DerivedEngineeringCRSNNPtr createDerivedEngineeringCRS() { auto derivingConversion = Conversion::create( diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 97cdca11a3..7775416fd4 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -10585,6 +10585,7 @@ TEST(io, createFromUserInput) { EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 4400); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 16031); } + { // DerivedVerticalCRS based on "NAVD88 height", using a foot UP axis, // and EPSG:7813 "Vertical Axis Unit Conversion" conversion @@ -10594,11 +10595,71 @@ TEST(io, createFromUserInput) { dbContext); auto crs = nn_dynamic_pointer_cast(obj); ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 height (ft)"); EXPECT_EQ(crs->baseCRS()->getEPSGCode(), 5703); EXPECT_EQ(crs->coordinateSystem()->getEPSGCode(), 1030); EXPECT_EQ(crs->derivingConversion()->getEPSGCode(), 7813); } + { + // DerivedVerticalCRS based on "NAVD88 height", using a ftUS UP axis, + // and EPSG:7813 "Vertical Axis Unit Conversion" conversion + auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::5703," + "cs:EPSG::6497," + "coordinateOperation:EPSG::7813", + dbContext); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)"); + } + + { + // DerivedVerticalCRS based on "NAVD88 height (ftUS)", using a metre UP + // axis, and EPSG:7813 "Vertical Axis Unit Conversion" conversion + auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6360," + "cs:EPSG::6499," + "coordinateOperation:EPSG::7813", + dbContext); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 height"); + } + { + // DerivedVerticalCRS based on "NAVD88 height", using a metre DOWN axis, + // and EPSG:7812 "Height / Depth reversal" conversion + auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::5703," + "cs:EPSG::6498," + "coordinateOperation:EPSG::7812", + dbContext); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 depth"); + } + + { + // DerivedVerticalCRS based on "NAVD88 height (ftUS)", using a ftUS DOWN + // axis, and EPSG:7812 "Height / Depth reversal" conversion + auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6360," + "cs:EPSG::1043," + "coordinateOperation:EPSG::7812", + dbContext); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 depth (ftUS)"); + } + + { + // DerivedVerticalCRS based on "NAVD88 depth (ftUS)", using a ftUS UP + // axis, and EPSG:7812 "Height / Depth reversal" conversion + auto obj = createFromUserInput("urn:ogc:def:crs,crs:EPSG::6358," + "cs:EPSG::6497," + "coordinateOperation:EPSG::7812", + dbContext); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "NAVD88 height (ftUS)"); + } + { auto obj = createFromUserInput("urn:ogc:def:coordinateOperation," "coordinateOperation:EPSG::3895,"