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 /src | |
| 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
Diffstat (limited to 'src')
| -rw-r--r-- | src/iso19111/factory.cpp | 73 | ||||
| -rw-r--r-- | src/iso19111/io.cpp | 236 | ||||
| -rw-r--r-- | src/iso19111/static.cpp | 11 |
3 files changed, 315 insertions, 5 deletions
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, |
