diff --git a/include/proj/io.hpp b/include/proj/io.hpp index d343b620d7..cc03a1c8a8 100644 --- a/include/proj/io.hpp +++ b/include/proj/io.hpp @@ -778,6 +778,8 @@ class PROJ_GCC_DLL WKTParser { PROJ_DLL WKTParser &setStrict(bool strict); PROJ_DLL std::list warningList() const; + PROJ_DLL WKTParser &setUnsetIdentifiersIfIncompatibleDef(bool unset); + PROJ_DLL util::BaseObjectNNPtr createFromWKT(const std::string &wkt); // throw(ParsingException) diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index efec862257..5eb9d1e76e 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -599,6 +599,10 @@ PJ *proj_create(PJ_CONTEXT *ctx, const char *text) { * * @param out_warnings Pointer to a PROJ_STRING_LIST object, or NULL. * If provided, *out_warnings will contain a list of warnings, typically for @@ -639,6 +643,10 @@ PJ *proj_create_from_wkt(PJ_CONTEXT *ctx, const char *wkt, const char *value; if ((value = getOptionValue(*iter, "STRICT="))) { parser.setStrict(ci_equal(value, "YES")); + } else if ((value = getOptionValue( + *iter, "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF="))) { + parser.setUnsetIdentifiersIfIncompatibleDef( + ci_equal(value, "YES")); } else { std::string msg("Unknown option :"); msg += *iter; diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index cf4119a66e..6c5bc85b17 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -1280,6 +1280,7 @@ struct WKTParser::Private { }; bool strict_ = true; + bool unsetIdentifiersIfIncompatibleDef_ = true; std::list warningList_{}; std::vector toWGS84Parameters_{}; std::string datumPROJ4Grids_{}; @@ -1503,6 +1504,20 @@ WKTParser &WKTParser::setStrict(bool strict) { // --------------------------------------------------------------------------- +/** \brief Set whether object identifiers should be unset when there is + * a contradiction between the definition from WKT and the one from + * the database. + * + * At time of writing, this only applies to the base geographic CRS of a + * projected CRS, when comparing its coordinate system. + */ +WKTParser &WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool unset) { + d->unsetIdentifiersIfIncompatibleDef_ = unset; + return *this; +} + +// --------------------------------------------------------------------------- + /** \brief Return the list of warnings found during parsing. * * \note The list might be non-empty only is setStrict(false) has been called. @@ -3084,19 +3099,40 @@ WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) { !ellipsoidalCS->_isEquivalentTo( dbCRS->coordinateSystem().get(), util::IComparable::Criterion::EQUIVALENT)) { - emitRecoverableWarning( - "Coordinate system of GeographicCRS in the WKT " - "definition is different from the one of the " - "authority. Unsetting the identifier to avoid " - "confusion"); - props.unset(Identifier::CODESPACE_KEY); - props.unset(Identifier::AUTHORITY_KEY); - props.unset(IdentifiedObject::IDENTIFIERS_KEY); + if (unsetIdentifiersIfIncompatibleDef_) { + emitRecoverableWarning( + "Coordinate system of GeographicCRS in the WKT " + "definition is different from the one of the " + "authority. Unsetting the identifier to avoid " + "confusion"); + props.unset(Identifier::CODESPACE_KEY); + props.unset(Identifier::AUTHORITY_KEY); + props.unset(IdentifiedObject::IDENTIFIERS_KEY); + } crs = GeographicCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); } else if (dbCRS) { + auto csFromDB = dbCRS->coordinateSystem(); + auto csFromDBAltered = csFromDB; + if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) { + csFromDBAltered = + csFromDB->alterAngularUnit(angularUnit); + if (unsetIdentifiersIfIncompatibleDef_ && + !csFromDBAltered->_isEquivalentTo( + csFromDB.get(), + util::IComparable::Criterion::EQUIVALENT)) { + emitRecoverableWarning( + "Coordinate system of GeographicCRS in the WKT " + "definition is different from the one of the " + "authority. Unsetting the identifier to avoid " + "confusion"); + props.unset(Identifier::CODESPACE_KEY); + props.unset(Identifier::AUTHORITY_KEY); + props.unset(IdentifiedObject::IDENTIFIERS_KEY); + } + } crs = GeographicCRS::create(props, datum, datumEnsemble, - dbCRS->coordinateSystem()); + csFromDBAltered); } } return crs; diff --git a/test/unit/test_c_api.cpp b/test/unit/test_c_api.cpp index 7520e10b68..a70c092cc6 100644 --- a/test/unit/test_c_api.cpp +++ b/test/unit/test_c_api.cpp @@ -298,6 +298,39 @@ TEST_F(CApi, proj_create_from_wkt) { EXPECT_NE(errorList, nullptr); proj_string_list_destroy(errorList); } + { + PROJ_STRING_LIST warningList = nullptr; + PROJ_STRING_LIST errorList = nullptr; + const char *const options[] = { + "UNSET_IDENTIFIERS_IF_INCOMPATIBLE_DEF=NO", nullptr}; + auto wkt = "PROJCS[\"Merchich / Nord Maroc\"," + " GEOGCS[\"Merchich\"," + " DATUM[\"Merchich\"," + " SPHEROID[\"Clarke 1880 (IGN)\"," + "6378249.2,293.466021293627]]," + " PRIMEM[\"Greenwich\",0]," + " UNIT[\"grad\",0.015707963267949," + " AUTHORITY[\"EPSG\",\"9105\"]]," + " AUTHORITY[\"EPSG\",\"4261\"]]," + " PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," + " PARAMETER[\"latitude_of_origin\",37]," + " PARAMETER[\"central_meridian\",-6]," + " PARAMETER[\"scale_factor\",0.999625769]," + " PARAMETER[\"false_easting\",500000]," + " PARAMETER[\"false_northing\",300000]," + " UNIT[\"metre\",1," + " AUTHORITY[\"EPSG\",\"9001\"]]," + " AXIS[\"Easting\",EAST]," + " AXIS[\"Northing\",NORTH]]"; + auto obj = proj_create_from_wkt(m_ctxt, wkt, options, &warningList, + &errorList); + ObjectKeeper keeper(obj); + EXPECT_NE(obj, nullptr); + EXPECT_EQ(warningList, nullptr); + proj_string_list_destroy(warningList); + EXPECT_EQ(errorList, nullptr); + proj_string_list_destroy(errorList); + } { PROJ_STRING_LIST warningList = nullptr; PROJ_STRING_LIST errorList = nullptr; diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 3807abc1db..6135991661 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -1260,19 +1260,92 @@ TEST(wkt_parse, wkt1_projected_wrong_axis_geogcs) { " UNIT[\"metre\",1,\n" " AUTHORITY[\"EPSG\",\"9001\"]],\n" " AUTHORITY[\"EPSG\",\"32631\"]]"; - WKTParser parser; - parser.setStrict(false).attachDatabaseContext(DatabaseContext::create()); - auto obj = parser.createFromWKT(wkt); - EXPECT_TRUE(!parser.warningList().empty()); - auto crs = nn_dynamic_pointer_cast(obj); - ASSERT_TRUE(crs != nullptr); + { + WKTParser parser; + parser.setStrict(false).attachDatabaseContext( + DatabaseContext::create()); + auto obj = parser.createFromWKT(wkt); + EXPECT_TRUE(!parser.warningList().empty()); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); - EXPECT_TRUE(crs->baseCRS()->identifiers().empty()); + EXPECT_TRUE(crs->baseCRS()->identifiers().empty()); - auto cs = crs->baseCRS()->coordinateSystem(); - ASSERT_EQ(cs->axisList().size(), 2U); - EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST); - EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH); + auto cs = crs->baseCRS()->coordinateSystem(); + ASSERT_EQ(cs->axisList().size(), 2U); + EXPECT_EQ(cs->axisList()[0]->direction(), AxisDirection::EAST); + EXPECT_EQ(cs->axisList()[1]->direction(), AxisDirection::NORTH); + } + { + WKTParser parser; + parser.setStrict(false) + .setUnsetIdentifiersIfIncompatibleDef(false) + .attachDatabaseContext(DatabaseContext::create()); + auto obj = parser.createFromWKT(wkt); + EXPECT_TRUE(parser.warningList().empty()); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + EXPECT_TRUE(!crs->baseCRS()->identifiers().empty()); + } +} + +// --------------------------------------------------------------------------- + +TEST(wkt_parse, wkt1_projected_wrong_angular_unit) { + auto wkt = "PROJCS[\"Merchich / Nord Maroc\"," + " GEOGCS[\"Merchich\"," + " DATUM[\"Merchich\"," + " SPHEROID[\"Clarke 1880 (IGN)\"," + "6378249.2,293.466021293627]]," + " PRIMEM[\"Greenwich\",0]," + " UNIT[\"grad\",0.015707963267949," + " AUTHORITY[\"EPSG\",\"9105\"]]," + " AUTHORITY[\"EPSG\",\"4261\"]]," + " PROJECTION[\"Lambert_Conformal_Conic_1SP\"]," + " PARAMETER[\"latitude_of_origin\",37]," + " PARAMETER[\"central_meridian\",-6]," + " PARAMETER[\"scale_factor\",0.999625769]," + " PARAMETER[\"false_easting\",500000]," + " PARAMETER[\"false_northing\",300000]," + " UNIT[\"metre\",1," + " AUTHORITY[\"EPSG\",\"9001\"]]," + " AXIS[\"Easting\",EAST]," + " AXIS[\"Northing\",NORTH]]"; + { + WKTParser parser; + parser.setStrict(false).attachDatabaseContext( + DatabaseContext::create()); + auto obj = parser.createFromWKT(wkt); + EXPECT_TRUE(!parser.warningList().empty()); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + // No base CRS identifiers + EXPECT_TRUE(crs->baseCRS()->identifiers().empty()); + + auto cs = crs->baseCRS()->coordinateSystem(); + ASSERT_EQ(cs->axisList().size(), 2U); + EXPECT_NEAR(cs->axisList()[0]->unit().conversionToSI(), + UnitOfMeasure::GRAD.conversionToSI(), 1e-10); + } + { + WKTParser parser; + parser.setUnsetIdentifiersIfIncompatibleDef(false) + .attachDatabaseContext(DatabaseContext::create()); + auto obj = parser.createFromWKT(wkt); + EXPECT_TRUE(parser.warningList().empty()); + auto crs = nn_dynamic_pointer_cast(obj); + ASSERT_TRUE(crs != nullptr); + + // Base CRS identifier preserved + EXPECT_TRUE(!crs->baseCRS()->identifiers().empty()); + + auto cs = crs->baseCRS()->coordinateSystem(); + ASSERT_EQ(cs->axisList().size(), 2U); + EXPECT_NEAR(cs->axisList()[0]->unit().conversionToSI(), + UnitOfMeasure::GRAD.conversionToSI(), 1e-10); + } } // ---------------------------------------------------------------------------