diff options
| author | Even Rouault <even.rouault@spatialys.com> | 2021-04-10 20:15:34 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-04-10 20:15:34 +0200 |
| commit | 34ea13913717b20982778d5e5f13b4a5ee8288ab (patch) | |
| tree | c16c443866a0eec19c488e415fc75815a090327e | |
| parent | 84a679954b1fa4c41c7bdbac87013be78b64bea4 (diff) | |
| parent | a77ce5cf343c00fc95b5f3d9b2c27e02b6205fd3 (diff) | |
| download | PROJ-34ea13913717b20982778d5e5f13b4a5ee8288ab.tar.gz PROJ-34ea13913717b20982778d5e5f13b4a5ee8288ab.zip | |
Merge pull request #2656 from rouault/createFromUserInput_improvements
createFromUserInput(): various enhancements
| -rw-r--r-- | include/proj/common.hpp | 2 | ||||
| -rw-r--r-- | src/iso19111/factory.cpp | 73 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 236 | ||||
| -rw-r--r-- | src/iso19111/static.cpp | 11 | ||||
| -rw-r--r-- | test/unit/test_factory.cpp | 23 | ||||
| -rw-r--r-- | test/unit/test_io.cpp | 248 |
6 files changed, 588 insertions, 5 deletions
diff --git a/include/proj/common.hpp b/include/proj/common.hpp index c8ffe904..4cc1d63d 100644 --- a/include/proj/common.hpp +++ b/include/proj/common.hpp @@ -125,6 +125,8 @@ class PROJ_GCC_DLL UnitOfMeasure : public util::BaseObject { PROJ_DLL static const UnitOfMeasure METRE; PROJ_DLL static const UnitOfMeasure METRE_PER_YEAR; + PROJ_DLL static const UnitOfMeasure FOOT; + PROJ_DLL static const UnitOfMeasure US_FOOT; PROJ_DLL static const UnitOfMeasure RADIAN; PROJ_DLL static const UnitOfMeasure MICRORADIAN; diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index fa2a0ef3..3367f55c 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -4951,6 +4951,79 @@ AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, if (crs) { return NN_NO_CHECK(crs); } + + if (d->authority() == metadata::Identifier::OGC) { + if (code == "AnsiDate") { + // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate + return crs::TemporalCRS::create( + util::PropertyMap() + // above URL indicates Julian Date" as name... likely wrong + .set(common::IdentifiedObject::NAME_KEY, "Ansi Date") + .set(metadata::Identifier::CODESPACE_KEY, d->authority()) + .set(metadata::Identifier::CODE_KEY, code), + datum::TemporalDatum::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) " + "as day 1."), + common::DateTime::create("1600-12-31T00:00:00Z"), + datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), + cs::TemporalCountCS::create( + util::PropertyMap(), + cs::CoordinateSystemAxis::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, "Time"), + "T", cs::AxisDirection::FUTURE, + common::UnitOfMeasure("day", 0, + UnitOfMeasure::Type::TIME)))); + } + if (code == "JulianDate") { + // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate + return crs::TemporalCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, "Julian Date") + .set(metadata::Identifier::CODESPACE_KEY, d->authority()) + .set(metadata::Identifier::CODE_KEY, code), + datum::TemporalDatum::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, + "The beginning of the Julian period."), + common::DateTime::create("-4714-11-24T12:00:00Z"), + datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), + cs::TemporalCountCS::create( + util::PropertyMap(), + cs::CoordinateSystemAxis::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, "Time"), + "T", cs::AxisDirection::FUTURE, + common::UnitOfMeasure("day", 0, + UnitOfMeasure::Type::TIME)))); + } + if (code == "UnixTime") { + // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime + return crs::TemporalCRS::create( + util::PropertyMap() + .set(common::IdentifiedObject::NAME_KEY, "Unix Time") + .set(metadata::Identifier::CODESPACE_KEY, d->authority()) + .set(metadata::Identifier::CODE_KEY, code), + datum::TemporalDatum::create( + util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, + "Unix epoch"), + common::DateTime::create("1970-01-01T00:00:00Z"), + datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN), + cs::TemporalCountCS::create( + util::PropertyMap(), + cs::CoordinateSystemAxis::create( + util::PropertyMap().set( + common::IdentifiedObject::NAME_KEY, "Time"), + "T", cs::AxisDirection::FUTURE, + common::UnitOfMeasure::SECOND))); + } + if (code == "84") { + return createCoordinateReferenceSystem("CRS84", false); + } + } + auto res = d->runWithCodeParam( "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code); if (res.empty()) { diff --git a/src/iso19111/io.cpp b/src/iso19111/io.cpp index 184d992f..bd29c310 100644 --- a/src/iso19111/io.cpp +++ b/src/iso19111/io.cpp @@ -6126,6 +6126,188 @@ EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) { // --------------------------------------------------------------------------- +// import a CRS encoded as OGC Best Practice document 11-135. + +static const char *const crsURLPrefixes[] = { + "http://opengis.net/def/crs", "https://opengis.net/def/crs", + "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs", + "www.opengis.net/def/crs", +}; + +static bool isCRSURL(const std::string &text) { + for (const auto crsURLPrefix : crsURLPrefixes) { + if (starts_with(text, crsURLPrefix)) { + return true; + } + } + return false; +} + +static CRSNNPtr importFromCRSURL(const std::string &text, + const DatabaseContextNNPtr &dbContext) { + // e.g http://www.opengis.net/def/crs/EPSG/0/4326 + std::vector<std::string> parts; + for (const auto crsURLPrefix : crsURLPrefixes) { + if (starts_with(text, crsURLPrefix)) { + parts = split(text.substr(strlen(crsURLPrefix)), '/'); + break; + } + } + + // e.g + // "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855" + if (!parts.empty() && starts_with(parts[0], "-compound?")) { + parts = split(text.substr(text.find('?') + 1), '&'); + std::map<int, std::string> mapParts; + for (const auto &part : parts) { + const auto queryParam = split(part, '='); + if (queryParam.size() != 2) { + throw ParsingException("invalid OGC CRS URL"); + } + try { + mapParts[std::stoi(queryParam[0])] = queryParam[1]; + } catch (const std::exception &) { + throw ParsingException("invalid OGC CRS URL"); + } + } + std::vector<CRSNNPtr> components; + std::string name; + for (size_t i = 1; i <= mapParts.size(); ++i) { + const auto iter = mapParts.find(static_cast<int>(i)); + if (iter == mapParts.end()) { + throw ParsingException("invalid OGC CRS URL"); + } + components.emplace_back(importFromCRSURL(iter->second, dbContext)); + if (!name.empty()) { + name += " + "; + } + name += components.back()->nameStr(); + } + return CompoundCRS::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, name), + components); + } + + if (parts.size() < 4) { + throw ParsingException("invalid OGC CRS URL"); + } + + const auto &auth_name = parts[1]; + const auto &code = parts[3]; + auto factoryCRS = AuthorityFactory::create(dbContext, auth_name); + return factoryCRS->createCoordinateReferenceSystem(code, true); +} + +// --------------------------------------------------------------------------- + +/* Import a CRS encoded as WMSAUTO string. + * + * Note that the WMS 1.3 specification does not include the + * units code, while apparently earlier specs do. We try to + * guess around this. + * + * (code derived from GDAL's importFromWMSAUTO()) + */ + +static CRSNNPtr importFromWMSAUTO(const std::string &text) { + + int nUnitsId = 9001; + double dfRefLong; + double dfRefLat = 0.0; + + assert(ci_starts_with(text, "AUTO:")); + const auto parts = split(text.substr(strlen("AUTO:")), ','); + + try { + constexpr int AUTO_MOLLWEIDE = 42005; + if (parts.size() == 4) { + nUnitsId = std::stoi(parts[1]); + dfRefLong = c_locale_stod(parts[2]); + dfRefLat = c_locale_stod(parts[3]); + } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) { + nUnitsId = std::stoi(parts[1]); + dfRefLong = c_locale_stod(parts[2]); + } else if (parts.size() == 3) { + dfRefLong = c_locale_stod(parts[1]); + dfRefLat = c_locale_stod(parts[2]); + } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) { + dfRefLong = c_locale_stod(parts[1]); + } else { + throw ParsingException("invalid WMS AUTO CRS definition"); + } + + const auto getConversion = [=]() { + const int nProjId = std::stoi(parts[0]); + switch (nProjId) { + case 42001: // Auto UTM + if (!(dfRefLong >= -180 && dfRefLong < 180)) { + throw ParsingException("invalid WMS AUTO CRS definition: " + "invalid longitude"); + } + return Conversion::createUTM( + util::PropertyMap(), + static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1, + dfRefLat >= 0.0); + + case 42002: // Auto TM (strangely very UTM-like). + return Conversion::createTransverseMercator( + util::PropertyMap(), common::Angle(0), + common::Angle(dfRefLong), common::Scale(0.9996), + common::Length(500000), + common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0)); + + case 42003: // Auto Orthographic. + return Conversion::createOrthographic( + util::PropertyMap(), common::Angle(dfRefLat), + common::Angle(dfRefLong), common::Length(0), + common::Length(0)); + + case 42004: // Auto Equirectangular + return Conversion::createEquidistantCylindrical( + util::PropertyMap(), common::Angle(dfRefLat), + common::Angle(dfRefLong), common::Length(0), + common::Length(0)); + + case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant + return Conversion::createMollweide( + util::PropertyMap(), common::Angle(dfRefLong), + common::Length(0), common::Length(0)); + + default: + throw ParsingException("invalid WMS AUTO CRS definition: " + "unsupported projection id"); + } + }; + + const auto getUnits = [=]() { + switch (nUnitsId) { + case 9001: + return UnitOfMeasure::METRE; + + case 9002: + return UnitOfMeasure::FOOT; + + case 9003: + return UnitOfMeasure::US_FOOT; + + default: + throw ParsingException("invalid WMS AUTO CRS definition: " + "unsupported units code"); + } + }; + + return crs::ProjectedCRS::create( + util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), + crs::GeographicCRS::EPSG_4326, getConversion(), + cs::CartesianCS::createEastingNorthing(getUnits())); + + } catch (const std::exception &) { + throw ParsingException("invalid WMS AUTO CRS definition"); + } +} + +// --------------------------------------------------------------------------- + static BaseObjectNNPtr createFromUserInput(const std::string &text, const DatabaseContextPtr &dbContext, bool usePROJ4InitRules, @@ -6187,6 +6369,14 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, .createFromPROJString(text); } + if (isCRSURL(text) && dbContext) { + return importFromCRSURL(text, NN_NO_CHECK(dbContext)); + } + + if (ci_starts_with(text, "AUTO:")) { + return importFromWMSAUTO(text); + } + auto tokens = split(text, ':'); if (tokens.size() == 2) { if (!dbContext) { @@ -6406,15 +6596,14 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, return ConcatenatedOperation::createComputeMetadata(components, true); } - // urn:ogc:def:crs:EPSG::4326 - if (tokens.size() == 7) { + const auto createFromURNPart = + [&dbContext](const std::string &type, const std::string &authName, + const std::string &code) -> BaseObjectNNPtr { if (!dbContext) { throw ParsingException("no database context specified"); } - const auto &type = tokens[3]; auto factory = - AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[4]); - const auto &code = tokens[6]; + AuthorityFactory::create(NN_NO_CHECK(dbContext), authName); if (type == "crs") { return factory->createCoordinateReferenceSystem(code); } @@ -6434,6 +6623,39 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, return factory->createPrimeMeridian(code); } throw ParsingException(concat("unhandled object type: ", type)); + }; + + // urn:ogc:def:crs:EPSG::4326 + if (tokens.size() == 7 && tokens[0] == "urn") { + + const auto &type = tokens[3]; + const auto &authName = tokens[4]; + const auto &code = tokens[6]; + return createFromURNPart(type, authName, code); + } + + // urn:ogc:def:crs:OGC::AUTO42001:-117:33 + if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" && + ci_starts_with(tokens[6], "AUTO")) { + const auto textAUTO = text.substr(text.find(":AUTO") + 5); + return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ",")); + } + + // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to + // above) + if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") { + const auto &type = tokens[2]; + const auto &authName = tokens[3]; + const auto &code = tokens[5]; + return createFromURNPart(type, authName, code); + } + + // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version) + if (tokens.size() == 6 && tokens[0] == "urn") { + const auto &type = tokens[3]; + const auto &authName = tokens[4]; + const auto &code = tokens[5]; + return createFromURNPart(type, authName, code); } if (dbContext) { @@ -6590,6 +6812,10 @@ static BaseObjectNNPtr createFromUserInput(const std::string &text, * <li> OGC URN combining references for concatenated operations * e.g. * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li> + * <li>OGC URL for a single CRS. e.g. + * "http://www.opengis.net/def/crs/EPSG/0/4326</li> <li>OGC URL for a compound + * CRS. e.g + * "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855"</li> * <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> diff --git a/src/iso19111/static.cpp b/src/iso19111/static.cpp index d099cd6c..e4467882 100644 --- a/src/iso19111/static.cpp +++ b/src/iso19111/static.cpp @@ -317,6 +317,17 @@ const UnitOfMeasure UnitOfMeasure::METRE("metre", 1.0, UnitOfMeasure::Type::LINEAR, Identifier::EPSG, "9001"); +/** \brief Foot, unit of measure of type LINEAR. */ +const UnitOfMeasure UnitOfMeasure::FOOT("foot", 0.3048, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "9002"); + +/** \brief US survey foot, unit of measure of type LINEAR. */ +const UnitOfMeasure UnitOfMeasure::US_FOOT("US survey foot", + 0.304800609601219241184, + UnitOfMeasure::Type::LINEAR, + Identifier::EPSG, "9003"); + /** \brief Degree, unit of measure of type ANGULAR. */ const UnitOfMeasure UnitOfMeasure::DEGREE("degree", M_PI / 180., UnitOfMeasure::Type::ANGULAR, diff --git a/test/unit/test_factory.cpp b/test/unit/test_factory.cpp index c67f1490..edc8b190 100644 --- a/test/unit/test_factory.cpp +++ b/test/unit/test_factory.cpp @@ -4244,4 +4244,27 @@ TEST(factory, objectInsertion) { } } +// --------------------------------------------------------------------------- + +TEST(factory, ogc_timecrs) { + auto ctxt = DatabaseContext::create(); + auto factory = AuthorityFactory::create(ctxt, Identifier::OGC); + factory->createCoordinateReferenceSystem("AnsiDate"); + factory->createCoordinateReferenceSystem("JulianDate"); + factory->createCoordinateReferenceSystem("UnixTime"); +} + +// --------------------------------------------------------------------------- + +TEST(factory, ogc_crs) { + auto ctxt = DatabaseContext::create(); + auto factory = AuthorityFactory::create(ctxt, Identifier::OGC); + factory->createCoordinateReferenceSystem("CRS84"); + factory->createCoordinateReferenceSystem("84"); + factory->createCoordinateReferenceSystem("CRS27"); + factory->createCoordinateReferenceSystem("CRS83"); +} + +// --------------------------------------------------------------------------- + } // namespace diff --git a/test/unit/test_io.cpp b/test/unit/test_io.cpp index 9d627ce9..780db16d 100644 --- a/test/unit/test_io.cpp +++ b/test/unit/test_io.cpp @@ -10349,6 +10349,8 @@ TEST(io, createFromUserInput) { EXPECT_NO_THROW(createFromUserInput("epsg:4326", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:crs:EPSG::4326", dbContext)); + EXPECT_THROW(createFromUserInput("urn:ogc:def:crs:EPSG::4326", nullptr), + ParsingException); EXPECT_NO_THROW(createFromUserInput( "urn:ogc:def:coordinateOperation:EPSG::1671", dbContext)); EXPECT_NO_THROW( @@ -10357,6 +10359,22 @@ TEST(io, createFromUserInput) { createFromUserInput("urn:ogc:def:meridian:EPSG::8901", dbContext)); EXPECT_NO_THROW( createFromUserInput("urn:ogc:def:ellipsoid:EPSG::7030", dbContext)); + + // Legacy formulations + EXPECT_NO_THROW( + createFromUserInput("urn:x-ogc:def:crs:EPSG::4326", dbContext)); + EXPECT_NO_THROW( + createFromUserInput("urn:opengis:def:crs:EPSG::4326", dbContext)); + EXPECT_NO_THROW( + createFromUserInput("urn:opengis:crs:EPSG::4326", dbContext)); + EXPECT_NO_THROW( + createFromUserInput("urn:x-ogc:def:crs:EPSG:4326", dbContext)); + EXPECT_THROW(createFromUserInput("urn:opengis:crs:EPSG::4326", nullptr), + ParsingException); + EXPECT_THROW( + createFromUserInput("urn:opengis:unhandled:EPSG::4326", dbContext), + ParsingException); + { auto obj = createFromUserInput("EPSG:2393+5717", dbContext); auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj); @@ -10637,6 +10655,236 @@ TEST(io, createFromUserInput) { // --------------------------------------------------------------------------- +TEST(io, createFromUserInput_ogc_crs_url) { + auto dbContext = DatabaseContext::create(); + + { + auto obj = createFromUserInput( + "http://www.opengis.net/def/crs/EPSG/0/4326", dbContext); + auto crs = nn_dynamic_pointer_cast<GeographicCRS>(obj); + ASSERT_TRUE(crs != nullptr); + } + + EXPECT_THROW( + createFromUserInput("http://www.opengis.net/def/crs", dbContext), + ParsingException); + + EXPECT_THROW( + createFromUserInput("http://www.opengis.net/def/crs/EPSG/0", dbContext), + ParsingException); + + EXPECT_THROW(createFromUserInput( + "http://www.opengis.net/def/crs/EPSG/0/XXXX", dbContext), + NoSuchAuthorityCodeException); + + { + auto obj = createFromUserInput( + "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" + "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/3855", + dbContext); + auto crs = nn_dynamic_pointer_cast<CompoundCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->nameStr(), "WGS 84 + EGM2008 height"); + } + + // No part + EXPECT_THROW(createFromUserInput("http://www.opengis.net/def/crs-compound?", + dbContext), + ParsingException); + + // Just one part + EXPECT_THROW( + createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" + "www.opengis.net/def/crs/EPSG/0/4326", + dbContext), + InvalidCompoundCRSException); + + // Invalid compound CRS + EXPECT_THROW( + createFromUserInput( + "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" + "def/crs/EPSG/0/4326&2=http://www.opengis.net/def/crs/EPSG/0/4326", + dbContext), + InvalidCompoundCRSException); + + // Missing 2= + EXPECT_THROW( + createFromUserInput( + "http://www.opengis.net/def/crs-compound?1=http://www.opengis.net/" + "def/crs/EPSG/0/4326&3=http://www.opengis.net/def/crs/EPSG/0/3855", + dbContext), + ParsingException); + + // Invalid query parameter + EXPECT_THROW( + createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" + "www.opengis.net/def/crs/EPSG/0/4326&bla", + dbContext), + ParsingException); + + // Invalid query parameter + EXPECT_THROW( + createFromUserInput("http://www.opengis.net/def/crs-compound?1=http://" + "www.opengis.net/def/crs/EPSG/0/4326&two=http://" + "www.opengis.net/def/crs/EPSG/0/3855", + dbContext), + ParsingException); +} + +// --------------------------------------------------------------------------- + +TEST(io, createFromUserInput_OGC_AUTO) { + + // UTM north + { + auto obj = createFromUserInput("AUTO:42001,-117,33", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); + EXPECT_EQ( + crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); + } + + // UTM south + { + auto obj = createFromUserInput("AUTO:42001,-117,-33", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11S"); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +south +datum=WGS84 +units=m +no_defs " + "+type=crs"); + } + + // Explicit unit: metre + { + auto obj = createFromUserInput("AUTO:42001,9001,-117,33", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); + EXPECT_EQ( + crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); + } + + // Explicit unit: foot + { + auto obj = createFromUserInput("AUTO:42001,9002,-117,33", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); + EXPECT_EQ( + crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +datum=WGS84 +units=ft +no_defs +type=crs"); + } + + // Explicit unit: US survery foot + { + auto obj = createFromUserInput("AUTO:42001,9003,-117,33", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "UTM zone 11N"); + EXPECT_EQ( + crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +datum=WGS84 +units=us-ft +no_defs +type=crs"); + } + + // Explicit unit: invalid + EXPECT_THROW(createFromUserInput("AUTO:42001,0,-117,33", nullptr), + ParsingException); + + // Invalid longitude + EXPECT_THROW(createFromUserInput("AUTO:42001,-180.01,33", nullptr), + ParsingException); + EXPECT_NO_THROW(createFromUserInput("AUTO:42001,-180,33", nullptr)); + EXPECT_THROW(createFromUserInput("AUTO:42001,180,33", nullptr), + ParsingException); + EXPECT_NO_THROW(createFromUserInput("AUTO:42001,179.999,33", nullptr)); + + // Too short + EXPECT_THROW(createFromUserInput("AUTO:42001", nullptr), ParsingException); + + // TMerc / north + { + auto obj = createFromUserInput("AUTO:42002,1,2", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "Transverse Mercator"); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=tmerc +lat_0=0 +lon_0=1 +k=0.9996 +x_0=500000 +y_0=0 " + "+datum=WGS84 +units=m +no_defs +type=crs"); + } + + // TMerc / south + { + auto obj = createFromUserInput("AUTO:42002,1,-2", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->derivingConversion()->nameStr(), "Transverse Mercator"); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=tmerc +lat_0=0 +lon_0=1 +k=0.9996 +x_0=500000 " + "+y_0=10000000 +datum=WGS84 +units=m +no_defs +type=crs"); + } + + // Orthographic + { + auto obj = createFromUserInput("AUTO:42003,1,2", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=ortho +lat_0=2 +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 " + "+units=m +no_defs +type=crs"); + } + + // Equirectangular + { + auto obj = createFromUserInput("AUTO:42004,1,0", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=1 +x_0=0 +y_0=0 " + "+datum=WGS84 +units=m +no_defs +type=crs"); + } + + // Mollweide + { + auto obj = createFromUserInput("AUTO:42005,1", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=moll +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m " + "+no_defs +type=crs"); + } + + // Mollweide with explicit unit + { + auto obj = createFromUserInput("AUTO:42005,9001,1", nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ(crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=moll +lon_0=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m " + "+no_defs +type=crs"); + } + + // Invalid method id + EXPECT_THROW(createFromUserInput("AUTO:42999,1,0", nullptr), + ParsingException); + + // As urn:ogc:def:crs:OGC::AUTOxxxx:.... + { + auto obj = createFromUserInput("urn:ogc:def:crs:OGC::AUTO42001:-117:33", + nullptr); + auto crs = nn_dynamic_pointer_cast<ProjectedCRS>(obj); + ASSERT_TRUE(crs != nullptr); + EXPECT_EQ( + crs->exportToPROJString(PROJStringFormatter::create().get()), + "+proj=utm +zone=11 +datum=WGS84 +units=m +no_defs +type=crs"); + } +} + +// --------------------------------------------------------------------------- + TEST(io, createFromUserInput_hack_EPSG_102100) { auto dbContext = DatabaseContext::create(); auto obj = createFromUserInput("EPSG:102100", dbContext); |
