diff options
| -rw-r--r-- | docs/source/apps/projinfo.rst | 3 | ||||
| -rw-r--r-- | src/iso19111/c_api.cpp | 4 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 107 | ||||
| -rw-r--r-- | test/unit/test_io.cpp | 30 |
4 files changed, 122 insertions, 22 deletions
diff --git a/docs/source/apps/projinfo.rst b/docs/source/apps/projinfo.rst index 91d09941..0e54c244 100644 --- a/docs/source/apps/projinfo.rst +++ b/docs/source/apps/projinfo.rst @@ -36,6 +36,8 @@ Synopsis - a WKT string, - an object code (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", "urn:ogc:def:coordinateOperation:EPSG::1671"), + - an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as + uniqueness is not guaranteed, heuristics are applied to determine the appropriate best match. - a OGC URN combining references for compound coordinate reference systems (e.g "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717" or custom abbreviated syntax "EPSG:2393+5717"), @@ -46,6 +48,7 @@ Synopsis - a OGC URN combining references for concatenated operations (e.g. "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618") - a PROJJSON string. The jsonschema is at https://proj.org/schemas/v0.1/projjson.schema.json (*added in 6.2*) + - a compound CRS made from two object names separated with " + ". e.g. "WGS 84 + EGM96 height" (*added in 7.1*) {object_reference} is a filename preceded by the '@' character. The file referenced by the {object_reference} must contain a valid diff --git a/src/iso19111/c_api.cpp b/src/iso19111/c_api.cpp index 17fbd079..5a3f0374 100644 --- a/src/iso19111/c_api.cpp +++ b/src/iso19111/c_api.cpp @@ -427,7 +427,9 @@ PJ *proj_clone(PJ_CONTEXT *ctx, const PJ *obj) { /** \brief Instantiate an object from a WKT string, PROJ string, object code * (like "EPSG:4326", "urn:ogc:def:crs:EPSG::4326", - * "urn:ogc:def:coordinateOperation:EPSG::1671") or PROJJSON string. + * "urn:ogc:def:coordinateOperation:EPSG::1671"), a PROJJSON string, an object + * name (e.g "WGS 84") of a compound CRS build from object names + * (e.g "WGS 84 + EGM96 height") * * This function calls osgeo::proj::io::createFromUserInput() * diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 8bfb4395..dfc1aa27 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -6025,32 +6025,20 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, if (dbContext) { auto factory = AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string()); - // First pass: exact match on CRS objects - // Second pass: exact match on other objects - // Third pass: approximate match on CRS objects - // Fourth pass: approximate match on other objects - constexpr size_t limitResultCount = 10; - for (int pass = 0; pass <= 3; ++pass) { - const bool approximateMatch = (pass >= 2); + + const auto searchObject = [&factory]( + const std::string &objectName, bool approximateMatch, + const std::vector<AuthorityFactory::ObjectType> &objectTypes, + bool &goOn) { + constexpr size_t limitResultCount = 10; auto res = factory->createObjectsFromName( - text, - (pass == 0 || pass == 2) - ? std::vector< - AuthorityFactory::ObjectType>{AuthorityFactory:: - ObjectType::CRS} - : std::vector< - AuthorityFactory:: - ObjectType>{AuthorityFactory::ObjectType:: - ELLIPSOID, - AuthorityFactory::ObjectType::DATUM, - AuthorityFactory::ObjectType:: - COORDINATE_OPERATION}, - approximateMatch, limitResultCount); + objectName, objectTypes, approximateMatch, limitResultCount); if (res.size() == 1) { return res.front(); } if (res.size() > 1) { - if (pass == 0 || pass == 2) { + if (objectTypes.size() == 1 && + objectTypes[0] == AuthorityFactory::ObjectType::CRS) { for (size_t ndim = 2; ndim <= 3; ndim++) { for (const auto &obj : res) { auto crs = @@ -6079,6 +6067,79 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, } throw ParsingException(msg); } + goOn = true; + throw ParsingException("dummy"); + }; + + const auto searchCRS = [&searchObject](const std::string &objectName) { + bool goOn = false; + const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{ + AuthorityFactory::ObjectType::CRS}; + try { + constexpr bool approximateMatch = false; + return searchObject(objectName, approximateMatch, objectTypes, + goOn); + } catch (const std::exception &) { + if (!goOn) + throw; + } + constexpr bool approximateMatch = true; + return searchObject(objectName, approximateMatch, objectTypes, + goOn); + }; + + // strings like "WGS 84 + EGM96 height" + CompoundCRSPtr compoundCRS; + try { + const auto tokensCompound = split(text, " + "); + if (tokensCompound.size() == 2) { + auto obj1 = searchCRS(tokensCompound[0]); + auto obj2 = searchCRS(tokensCompound[1]); + auto crs1 = util::nn_dynamic_pointer_cast<CRS>(obj1); + auto crs2 = util::nn_dynamic_pointer_cast<CRS>(obj2); + if (crs1 && crs2) { + compoundCRS = + CompoundCRS::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, + crs1->nameStr() + " + " + + crs2->nameStr()), + {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)}) + .as_nullable(); + } + } + } catch (const std::exception &) { + } + + // First pass: exact match on CRS objects + // Second pass: exact match on other objects + // Third pass: approximate match on CRS objects + // Fourth pass: approximate match on other objects + for (int pass = 0; pass <= 3; ++pass) { + const bool approximateMatch = (pass >= 2); + bool goOn = false; + try { + return searchObject( + text, approximateMatch, + (pass == 0 || pass == 2) + ? std::vector< + AuthorityFactory::ObjectType>{AuthorityFactory:: + ObjectType::CRS} + : std::vector< + AuthorityFactory:: + ObjectType>{AuthorityFactory::ObjectType:: + ELLIPSOID, + AuthorityFactory::ObjectType:: + DATUM, + AuthorityFactory::ObjectType:: + COORDINATE_OPERATION}, + goOn); + } catch (const std::exception &) { + if (!goOn) + throw; + } + if (compoundCRS) { + return NN_NO_CHECK(compoundCRS); + } } } @@ -6114,6 +6175,8 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>a compound CRS made from two object names separated with " + ". + * e.g. "WGS 84 + EGM96 height"</li> * <li>PROJJSON string</li> * </ul> * @@ -6163,6 +6226,8 @@ BaseObjectNNPtr createFromUserInput(const std::string &text, * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as * uniqueness is not guaranteed, the function may apply heuristics to * determine the appropriate best match.</li> + * <li>a compound CRS made from two object names separated with " + ". + * e.g. "WGS 84 + EGM96 height"</li> * <li>PROJJSON string</li> * </ul> * diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 9d6c2c43..6cdd3014 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -9548,6 +9548,36 @@ TEST(io, createFromUserInput) { ParsingException); EXPECT_NO_THROW(createFromUserInput("WGS84 UTM zone 31N", dbContext)); EXPECT_NO_THROW(createFromUserInput("ID74", dbContext)); + + { + // Exact match on each piece of the compound CRS + auto obj = createFromUserInput("WGS 84 + EGM96 height", dbContext); + auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM96 height"); + } + + { + // Aproximate match on each piece of the compound CRS + auto obj = createFromUserInput("WGS84 + EGM96", dbContext); + auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM96 height"); + } + + { + // Exact match of a CompoundCRS object + auto obj = createFromUserInput( + "WGS 84 / World Mercator + EGM2008 height", dbContext); + auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->identifiers().size(), 1U); + } + + EXPECT_THROW(createFromUserInput("WGS 84 + foobar", dbContext), + ParsingException); + EXPECT_THROW(createFromUserInput("foobar + EGM96 height", dbContext), + ParsingException); } // --------------------------------------------------------------------------- |
