/****************************************************************************** * * Project: PROJ * Purpose: ISO19111:2019 implementation * Author: Even Rouault * ****************************************************************************** * Copyright (c) 2018, Even Rouault * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #ifndef FROM_PROJ_CPP #define FROM_PROJ_CPP #endif #include "proj/common.hpp" #include "proj/coordinateoperation.hpp" #include "proj/coordinatesystem.hpp" #include "proj/crs.hpp" #include "proj/datum.hpp" #include "proj/io.hpp" #include "proj/metadata.hpp" #include "proj/util.hpp" #include "proj/internal/coordinateoperation_internal.hpp" #include "proj/internal/internal.hpp" #include "proj/internal/io_internal.hpp" #include "proj/internal/lru_cache.hpp" #include "proj/internal/tracing.hpp" #include "sqlite3_utils.hpp" #include #include #include #include #include #include #include #include #include // std::ostringstream #include #include #include "proj_constants.h" // PROJ include order is sensitive // clang-format off #include "proj.h" #include "proj_internal.h" #include "proj_api.h" // clang-format on #include // Custom SQLite VFS as our database is not supposed to be modified in // parallel. This is slightly faster #define ENABLE_CUSTOM_LOCKLESS_VFS using namespace NS_PROJ::internal; using namespace NS_PROJ::common; NS_PROJ_START namespace io { //! @cond Doxygen_Suppress // CRS subtypes #define GEOG_2D "geographic 2D" #define GEOG_3D "geographic 3D" #define GEOCENTRIC "geocentric" #define PROJECTED "projected" #define VERTICAL "vertical" #define COMPOUND "compound" #define GEOG_2D_SINGLE_QUOTED "'geographic 2D'" #define GEOG_3D_SINGLE_QUOTED "'geographic 3D'" #define GEOCENTRIC_SINGLE_QUOTED "'geocentric'" // --------------------------------------------------------------------------- struct SQLValues { enum class Type { STRING, DOUBLE }; // cppcheck-suppress noExplicitConstructor SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {} // cppcheck-suppress noExplicitConstructor SQLValues(double value) : type_(Type::DOUBLE), double_(value) {} const Type &type() const { return type_; } // cppcheck-suppress functionStatic const std::string &stringValue() const { return str_; } // cppcheck-suppress functionStatic double doubleValue() const { return double_; } private: Type type_; std::string str_{}; double double_ = 0.0; }; // --------------------------------------------------------------------------- using SQLRow = std::vector; using SQLResultSet = std::list; using ListOfParams = std::list; // --------------------------------------------------------------------------- struct DatabaseContext::Private { Private(); ~Private(); void open(const std::string &databasePath, PJ_CONTEXT *ctx); void setHandle(sqlite3 *sqlite_handle); sqlite3 *handle() const { return sqlite_handle_; } PJ_CONTEXT *pjCtxt() const { return pjCtxt_; } void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; } SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams(), bool useMaxFloatPrecision = false); std::vector getDatabaseStructure(); // cppcheck-suppress functionStatic const std::string &getPath() const { return databasePath_; } void attachExtraDatabases( const std::vector &auxiliaryDatabasePaths); // Mechanism to detect recursion in calls from // AuthorityFactory::createXXX() -> createFromUserInput() -> // AuthorityFactory::createXXX() struct RecursionDetector { explicit RecursionDetector(const DatabaseContextNNPtr &context) : dbContext_(context) { if (dbContext_->getPrivate()->recLevel_ == 2) { // Throw exception before incrementing, since the destructor // will not be called throw FactoryException("Too many recursive calls"); } ++dbContext_->getPrivate()->recLevel_; } ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; } private: DatabaseContextNNPtr dbContext_; }; std::map> &getMapCanonicalizeGRFName() { return mapCanonicalizeGRFName_; } // cppcheck-suppress functionStatic common::UnitOfMeasurePtr getUOMFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom); // cppcheck-suppress functionStatic crs::CRSPtr getCRSFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const crs::CRSNNPtr &crs); datum::GeodeticReferenceFramePtr // cppcheck-suppress functionStatic getGeodeticDatumFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum); datum::DatumEnsemblePtr // cppcheck-suppress functionStatic getDatumEnsembleFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble); datum::EllipsoidPtr // cppcheck-suppress functionStatic getEllipsoidFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid); datum::PrimeMeridianPtr // cppcheck-suppress functionStatic getPrimeMeridianFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm); // cppcheck-suppress functionStatic cs::CoordinateSystemPtr getCoordinateSystemFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs); // cppcheck-suppress functionStatic metadata::ExtentPtr getExtentFromCache(const std::string &code); // cppcheck-suppress functionStatic void cache(const std::string &code, const metadata::ExtentNNPtr &extent); // cppcheck-suppress functionStatic bool getCRSToCRSCoordOpFromCache( const std::string &code, std::vector &list); // cppcheck-suppress functionStatic void cache(const std::string &code, const std::vector &list); struct GridInfoCache { std::string fullFilename{}; std::string packageName{}; std::string url{}; bool found = false; bool directDownload = false; bool openLicense = false; bool gridAvailable = false; }; // cppcheck-suppress functionStatic bool getGridInfoFromCache(const std::string &code, GridInfoCache &info); // cppcheck-suppress functionStatic void cache(const std::string &code, const GridInfoCache &info); private: friend class DatabaseContext; std::string databasePath_{}; bool close_handle_ = true; sqlite3 *sqlite_handle_{}; std::map mapSqlToStatement_{}; PJ_CONTEXT *pjCtxt_ = nullptr; int recLevel_ = 0; bool detach_ = false; std::string lastMetadataValue_{}; std::map> mapCanonicalizeGRFName_{}; using LRUCacheOfObjects = lru11::Cache; static constexpr size_t CACHE_SIZE = 128; LRUCacheOfObjects cacheUOM_{CACHE_SIZE}; LRUCacheOfObjects cacheCRS_{CACHE_SIZE}; LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE}; LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE}; LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE}; LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE}; LRUCacheOfObjects cacheCS_{CACHE_SIZE}; LRUCacheOfObjects cacheExtent_{CACHE_SIZE}; lru11::Cache> cacheCRSToCrsCoordOp_{CACHE_SIZE}; lru11::Cache cacheGridInfo_{CACHE_SIZE}; std::map> cacheAllowedAuthorities_{}; lru11::Cache> cacheAliasNames_{ CACHE_SIZE}; static void insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj); static void getFromCache(LRUCacheOfObjects &cache, const std::string &code, util::BaseObjectPtr &obj); void closeDB() noexcept; // cppcheck-suppress functionStatic void registerFunctions(); #ifdef ENABLE_CUSTOM_LOCKLESS_VFS std::unique_ptr vfs_{}; #endif Private(const Private &) = delete; Private &operator=(const Private &) = delete; }; // --------------------------------------------------------------------------- DatabaseContext::Private::Private() = default; // --------------------------------------------------------------------------- DatabaseContext::Private::~Private() { assert(recLevel_ == 0); closeDB(); } // --------------------------------------------------------------------------- void DatabaseContext::Private::closeDB() noexcept { if (detach_) { // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes // a crash in TEST(factory, attachExtraDatabases_auxiliary) // due to possible wrong caching of key info. // The bug is specific to using a memory file with shared cache as an // auxiliary DB. // The efinitive fix was likely in 3.8.8 // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb // But just after 3.8.2, // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19 // also seemed to hide the issue. // Detaching a database hides the issue, not sure if it is by chance... try { run("DETACH DATABASE db_0"); } catch (...) { } detach_ = false; } for (auto &pair : mapSqlToStatement_) { sqlite3_finalize(pair.second); } mapSqlToStatement_.clear(); if (close_handle_ && sqlite_handle_ != nullptr) { sqlite3_close(sqlite_handle_); sqlite_handle_ = nullptr; } } // --------------------------------------------------------------------------- void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj) { cache.insert(code, obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache, const std::string &code, util::BaseObjectPtr &obj) { cache.tryGet(code, obj); } // --------------------------------------------------------------------------- bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache( const std::string &code, std::vector &list) { return cacheCRSToCrsCoordOp_.tryGet(code, list); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const std::vector &list) { cacheCRSToCrsCoordOp_.insert(code, list); } // --------------------------------------------------------------------------- crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheCRS_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const crs::CRSNNPtr &crs) { insertIntoCache(cacheCRS_, code, crs.as_nullable()); } // --------------------------------------------------------------------------- common::UnitOfMeasurePtr DatabaseContext::Private::getUOMFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheUOM_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom) { insertIntoCache(cacheUOM_, code, uom.as_nullable()); } // --------------------------------------------------------------------------- datum::GeodeticReferenceFramePtr DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheGeodeticDatum_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) { insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable()); } // --------------------------------------------------------------------------- datum::DatumEnsemblePtr DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheDatumEnsemble_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache( const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) { insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable()); } // --------------------------------------------------------------------------- datum::EllipsoidPtr DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheEllipsoid_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const datum::EllipsoidNNPtr &ellps) { insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable()); } // --------------------------------------------------------------------------- datum::PrimeMeridianPtr DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cachePrimeMeridian_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm) { insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable()); } // --------------------------------------------------------------------------- cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache( const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheCS_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs) { insertIntoCache(cacheCS_, code, cs.as_nullable()); } // --------------------------------------------------------------------------- metadata::ExtentPtr DatabaseContext::Private::getExtentFromCache(const std::string &code) { util::BaseObjectPtr obj; getFromCache(cacheExtent_, code, obj); return std::static_pointer_cast(obj); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const metadata::ExtentNNPtr &extent) { insertIntoCache(cacheExtent_, code, extent.as_nullable()); } // --------------------------------------------------------------------------- bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code, GridInfoCache &info) { return cacheGridInfo_.tryGet(code, info); } // --------------------------------------------------------------------------- void DatabaseContext::Private::cache(const std::string &code, const GridInfoCache &info) { cacheGridInfo_.insert(code, info); } // --------------------------------------------------------------------------- void DatabaseContext::Private::open(const std::string &databasePath, PJ_CONTEXT *ctx) { if (!ctx) { ctx = pj_get_default_ctx(); } const int sqlite3VersionNumber = sqlite3_libversion_number(); // Minimum version for correct performance: 3.11 if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) { pj_log(ctx, PJ_LOG_ERROR, "SQLite3 version is %s, whereas at least 3.11 should be used", sqlite3_libversion()); } setPjCtxt(ctx); std::string path(databasePath); if (path.empty()) { path.resize(2048); const bool found = pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0; path.resize(strlen(path.c_str())); if (!found) { throw FactoryException("Cannot find proj.db"); } } std::string vfsName; #ifdef ENABLE_CUSTOM_LOCKLESS_VFS if (ctx->custom_sqlite3_vfs_name.empty()) { vfs_ = SQLite3VFS::create(false, true, true); if (vfs_ == nullptr) { throw FactoryException("Open of " + path + " failed"); } vfsName = vfs_->name(); } else #endif { vfsName = ctx->custom_sqlite3_vfs_name; } if (sqlite3_open_v2(path.c_str(), &sqlite_handle_, SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX, vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK || !sqlite_handle_) { throw FactoryException("Open of " + path + " failed"); } databasePath_ = path; registerFunctions(); } // --------------------------------------------------------------------------- void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) { assert(sqlite_handle); assert(!sqlite_handle_); sqlite_handle_ = sqlite_handle; close_handle_ = false; registerFunctions(); } // --------------------------------------------------------------------------- std::vector DatabaseContext::Private::getDatabaseStructure() { const char *sqls[] = { "SELECT sql FROM sqlite_master WHERE type = 'table'", "SELECT sql FROM sqlite_master WHERE type = 'view'", "SELECT sql FROM sqlite_master WHERE type = 'trigger'"}; std::vector res; for (const auto &sql : sqls) { auto sqlRes = run(sql); for (const auto &row : sqlRes) { res.emplace_back(row[0]); } } return res; } // --------------------------------------------------------------------------- void DatabaseContext::Private::attachExtraDatabases( const std::vector &auxiliaryDatabasePaths) { assert(close_handle_); assert(sqlite_handle_); auto tables = run("SELECT name FROM sqlite_master WHERE type IN ('table', 'view')"); std::map> tableStructure; for (const auto &rowTable : tables) { auto tableName = rowTable[0]; auto tableInfo = run("PRAGMA table_info(\"" + replaceAll(tableName, "\"", "\"\"") + "\")"); for (const auto &rowCol : tableInfo) { const auto &colName = rowCol[1]; tableStructure[tableName].push_back(colName); } } closeDB(); sqlite3_open_v2(":memory:", &sqlite_handle_, SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX #ifdef SQLITE_OPEN_URI | SQLITE_OPEN_URI #endif , nullptr); if (!sqlite_handle_) { throw FactoryException("cannot create in memory database"); } run("ATTACH DATABASE '" + replaceAll(databasePath_, "'", "''") + "' AS db_0"); detach_ = true; int count = 1; for (const auto &otherDb : auxiliaryDatabasePaths) { std::string sql = "ATTACH DATABASE '"; sql += replaceAll(otherDb, "'", "''"); sql += "' AS db_"; sql += toString(static_cast(count)); count++; run(sql); } for (const auto &pair : tableStructure) { std::string sql("CREATE TEMP VIEW "); sql += pair.first; sql += " AS "; for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) { std::string selectFromAux("SELECT "); bool firstCol = true; for (const auto &colName : pair.second) { if (!firstCol) { selectFromAux += ", "; } firstCol = false; selectFromAux += colName; } selectFromAux += " FROM db_"; selectFromAux += toString(static_cast(i)); selectFromAux += "."; selectFromAux += pair.first; try { // Check that the request will succeed. In case of 'sparse' // databases... run(selectFromAux + " LIMIT 0"); if (i > 0) { sql += " UNION ALL "; } sql += selectFromAux; } catch (const std::exception &) { } } run(sql); } registerFunctions(); } // --------------------------------------------------------------------------- static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) { switch (sqlite3_value_type(val)) { case SQLITE_FLOAT: gotVal = true; return sqlite3_value_double(val); case SQLITE_INTEGER: gotVal = true; return static_cast(sqlite3_value_int64(val)); default: gotVal = false; return 0.0; } } // --------------------------------------------------------------------------- static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext, int /* argc */, sqlite3_value **argv) { bool b0, b1, b2, b3; double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0); double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1); double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2); double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3); if (!b0 || !b1 || !b2 || !b3) { sqlite3_result_null(pContext); return; } // Deal with area crossing antimeridian if (east_lon < west_lon) { east_lon += 360.0; } // Integrate cos(lat) between south_lat and north_lat double pseudo_area = (east_lon - west_lon) * (std::sin(common::Angle(north_lat).getSIValue()) - std::sin(common::Angle(south_lat).getSIValue())); sqlite3_result_double(pContext, pseudo_area); } // --------------------------------------------------------------------------- static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext, int /* argc */, sqlite3_value **argv) { bool b0, b1, b2, b3, b4, b5, b6, b7; double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0); double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1); double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2); double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3); double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4); double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5); double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6); double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7); if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) { sqlite3_result_null(pContext); return; } auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1, east_lon1, north_lat1); auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2, east_lon2, north_lat2); sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0); } // --------------------------------------------------------------------------- #ifndef SQLITE_DETERMINISTIC #define SQLITE_DETERMINISTIC 0 #endif void DatabaseContext::Private::registerFunctions() { sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, PROJ_SQLITE_pseudo_area_from_swne, nullptr, nullptr); sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8, SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr, PROJ_SQLITE_intersects_bbox, nullptr, nullptr); } // --------------------------------------------------------------------------- SQLResultSet DatabaseContext::Private::run(const std::string &sql, const ListOfParams ¶meters, bool useMaxFloatPrecision) { sqlite3_stmt *stmt = nullptr; auto iter = mapSqlToStatement_.find(sql); if (iter != mapSqlToStatement_.end()) { stmt = iter->second; sqlite3_reset(stmt); } else { if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(), static_cast(sql.size()), &stmt, nullptr) != SQLITE_OK) { throw FactoryException("SQLite error on " + sql + ": " + sqlite3_errmsg(sqlite_handle_)); } mapSqlToStatement_.insert( std::pair(sql, stmt)); } int nBindField = 1; for (const auto ¶m : parameters) { if (param.type() == SQLValues::Type::STRING) { auto strValue = param.stringValue(); sqlite3_bind_text(stmt, nBindField, strValue.c_str(), static_cast(strValue.size()), SQLITE_TRANSIENT); } else { assert(param.type() == SQLValues::Type::DOUBLE); sqlite3_bind_double(stmt, nBindField, param.doubleValue()); } nBindField++; } #ifdef TRACE_DATABASE size_t nPos = 0; std::string sqlSubst(sql); for (const auto ¶m : parameters) { nPos = sqlSubst.find('?', nPos); assert(nPos != std::string::npos); std::string strValue; if (param.type() == SQLValues::Type::STRING) { strValue = '\'' + param.stringValue() + '\''; } else { strValue = toString(param.doubleValue()); } sqlSubst = sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1); nPos += strValue.size(); } logTrace(sqlSubst, "DATABASE"); #endif SQLResultSet result; const int column_count = sqlite3_column_count(stmt); while (true) { int ret = sqlite3_step(stmt); if (ret == SQLITE_ROW) { SQLRow row(column_count); for (int i = 0; i < column_count; i++) { if (useMaxFloatPrecision && sqlite3_column_type(stmt, i) == SQLITE_FLOAT) { // sqlite3_column_text() does not use maximum precision std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << std::setprecision(18); buffer << sqlite3_column_double(stmt, i); row[i] = buffer.str(); } else { const char *txt = reinterpret_cast( sqlite3_column_text(stmt, i)); if (txt) { row[i] = txt; } } } result.emplace_back(std::move(row)); } else if (ret == SQLITE_DONE) { break; } else { throw FactoryException("SQLite error on " + sql + ": " + sqlite3_errmsg(sqlite_handle_)); } } return result; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatabaseContext::~DatabaseContext() = default; //! @endcond // --------------------------------------------------------------------------- DatabaseContext::DatabaseContext() : d(internal::make_unique()) {} // --------------------------------------------------------------------------- /** \brief Instantiate a database context. * * This database context should be used only by one thread at a time. * * @param databasePath Path and filename of the database. Might be empty * string for the default rules to locate the default proj.db * @param auxiliaryDatabasePaths Path and filename of auxiliary databases. * Might be empty. * @param ctx Context used for file search. * @throw FactoryException */ DatabaseContextNNPtr DatabaseContext::create(const std::string &databasePath, const std::vector &auxiliaryDatabasePaths, PJ_CONTEXT *ctx) { auto dbCtx = DatabaseContext::nn_make_shared(); dbCtx->getPrivate()->open(databasePath, ctx); if (!auxiliaryDatabasePaths.empty()) { dbCtx->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths); } return dbCtx; } // --------------------------------------------------------------------------- /** \brief Return the list of authorities used in the database. */ std::set DatabaseContext::getAuthorities() const { auto res = d->run("SELECT auth_name FROM authority_list"); std::set list; for (const auto &row : res) { list.insert(row[0]); } return list; } // --------------------------------------------------------------------------- /** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER, * CREATE VIEW) needed to initialize a new database. */ std::vector DatabaseContext::getDatabaseStructure() const { return d->getDatabaseStructure(); } // --------------------------------------------------------------------------- /** \brief Return the path to the database. */ const std::string &DatabaseContext::getPath() const { return d->getPath(); } // --------------------------------------------------------------------------- /** \brief Return a metadata item. * * Value remains valid while this is alive and to the next call to getMetadata */ const char *DatabaseContext::getMetadata(const char *key) const { auto res = d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)}); if (res.empty()) { return nullptr; } d->lastMetadataValue_ = res.front()[0]; return d->lastMetadataValue_.c_str(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) { auto ctxt = DatabaseContext::nn_make_shared(); ctxt->getPrivate()->setHandle(static_cast(sqlite_handle)); return ctxt; } // --------------------------------------------------------------------------- void *DatabaseContext::getSqliteHandle() const { return getPrivate()->handle(); } // --------------------------------------------------------------------------- bool DatabaseContext::lookForGridAlternative(const std::string &officialName, std::string &projFilename, std::string &projFormat, bool &inverse) const { auto res = d->run( "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM " "grid_alternatives WHERE original_grid_name = ? AND " "proj_grid_name <> ''", {officialName}); if (res.empty()) { return false; } const auto &row = res.front(); projFilename = row[0]; projFormat = row[1]; inverse = row[2] == "1"; return true; } // --------------------------------------------------------------------------- bool DatabaseContext::lookForGridInfo( const std::string &projFilename, bool considerKnownGridsAsAvailable, std::string &fullFilename, std::string &packageName, std::string &url, bool &directDownload, bool &openLicense, bool &gridAvailable) const { Private::GridInfoCache info; const std::string key(projFilename + (considerKnownGridsAsAvailable ? "true" : "false")); if (d->getGridInfoFromCache(key, info)) { fullFilename = info.fullFilename; packageName = info.packageName; url = info.url; directDownload = info.directDownload; openLicense = info.openLicense; gridAvailable = info.gridAvailable; return info.found; } fullFilename.clear(); packageName.clear(); url.clear(); openLicense = false; directDownload = false; if (considerKnownGridsAsAvailable) { fullFilename = projFilename; } else { fullFilename.resize(2048); if (d->pjCtxt() == nullptr) { d->setPjCtxt(pj_get_default_ctx()); } int errno_before = proj_context_errno(d->pjCtxt()); gridAvailable = pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0], fullFilename.size() - 1) != 0; proj_context_errno_set(d->pjCtxt(), errno_before); fullFilename.resize(strlen(fullFilename.c_str())); } auto res = d->run("SELECT " "grid_packages.package_name, " "grid_alternatives.url, " "grid_packages.url AS package_url, " "grid_alternatives.open_license, " "grid_packages.open_license AS package_open_license, " "grid_alternatives.direct_download, " "grid_packages.direct_download AS package_direct_download " "FROM grid_alternatives " "LEFT JOIN grid_packages ON " "grid_alternatives.package_name = grid_packages.package_name " "WHERE proj_grid_name = ? OR old_proj_grid_name = ?", {projFilename, projFilename}); bool ret = !res.empty(); if (ret) { const auto &row = res.front(); packageName = std::move(row[0]); url = row[1].empty() ? std::move(row[2]) : std::move(row[1]); openLicense = (row[3].empty() ? row[4] : row[3]) == "1"; directDownload = (row[5].empty() ? row[6] : row[5]) == "1"; if (considerKnownGridsAsAvailable && (!packageName.empty() || (!url.empty() && openLicense))) { gridAvailable = true; } info.fullFilename = fullFilename; info.packageName = packageName; info.url = url; info.directDownload = directDownload; info.openLicense = openLicense; } info.gridAvailable = gridAvailable; info.found = ret; d->cache(key, info); return ret; } // --------------------------------------------------------------------------- bool DatabaseContext::isKnownName(const std::string &name, const std::string &tableName) const { std::string sql("SELECT 1 FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE name = ? LIMIT 1"; return !d->run(sql, {name}).empty(); } // --------------------------------------------------------------------------- std::string DatabaseContext::getProjGridName(const std::string &oldProjGridName) { auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE " "old_proj_grid_name = ?", {oldProjGridName}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- std::string DatabaseContext::getOldProjGridName(const std::string &gridName) { auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE " "proj_grid_name = ?", {gridName}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- /** \brief Gets the alias name from an official name. * * @param officialName Official name. Mandatory * @param tableName Table name/category. Mandatory * @param source Source of the alias. Mandatory * @return Alias name (or empty if not found). * @throw FactoryException */ std::string DatabaseContext::getAliasFromOfficialName(const std::string &officialName, const std::string &tableName, const std::string &source) const { std::string sql("SELECT auth_name, code FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE name = ?"; if (tableName == "geodetic_crs") { sql += " AND type = " GEOG_2D_SINGLE_QUOTED; } auto res = d->run(sql, {officialName}); if (res.empty()) { res = d->run( "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND " "alt_name = ? AND source IN ('EPSG', 'PROJ')", {tableName, officialName}); if (res.size() != 1) { return std::string(); } } const auto &row = res.front(); res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND " "auth_name = ? AND code = ? AND source = ?", {tableName, row[0], row[1], source}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- /** \brief Gets the alias names for an object. * * Either authName + code or officialName must be non empty. * * @param authName Authority. * @param code Code. * @param officialName Official name. * @param tableName Table name/category. Mandatory * @param source Source of the alias. May be empty. * @return Aliases */ std::list DatabaseContext::getAliases( const std::string &authName, const std::string &code, const std::string &officialName, const std::string &tableName, const std::string &source) const { std::list res; const auto key(authName + code + officialName + tableName + source); if (d->cacheAliasNames_.tryGet(key, res)) { return res; } std::string resolvedAuthName(authName); std::string resolvedCode(code); if (authName.empty() || code.empty()) { std::string sql("SELECT auth_name, code FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE name = ?"; if (tableName == "geodetic_crs") { sql += " AND type = " GEOG_2D_SINGLE_QUOTED; } auto resSql = d->run(sql, {officialName}); if (resSql.empty()) { resSql = d->run("SELECT auth_name, code FROM alias_name WHERE " "table_name = ? AND " "alt_name = ? AND source IN ('EPSG', 'PROJ')", {tableName, officialName}); if (resSql.size() != 1) { d->cacheAliasNames_.insert(key, res); return res; } } const auto &row = resSql.front(); resolvedAuthName = row[0]; resolvedCode = row[1]; } std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND " "auth_name = ? AND code = ?"); ListOfParams params{tableName, resolvedAuthName, resolvedCode}; if (!source.empty()) { sql += " AND source = ?"; params.emplace_back(source); } auto resSql = d->run(sql, params); for (const auto &row : resSql) { res.emplace_back(row[0]); } d->cacheAliasNames_.insert(key, res); return res; } // --------------------------------------------------------------------------- /** \brief Return the 'text_definition' column of a table for an object * * @param tableName Table name/category. * @param authName Authority name of the object. * @param code Code of the object * @return Text definition (or empty) * @throw FactoryException */ std::string DatabaseContext::getTextDefinition(const std::string &tableName, const std::string &authName, const std::string &code) const { std::string sql("SELECT text_definition FROM \""); sql += replaceAll(tableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; auto res = d->run(sql, {authName, code}); if (res.empty()) { return std::string(); } return res.front()[0]; } // --------------------------------------------------------------------------- /** \brief Return the allowed authorities when researching transformations * between different authorities. * * @throw FactoryException */ std::vector DatabaseContext::getAllowedAuthorities( const std::string &sourceAuthName, const std::string &targetAuthName) const { const auto key(sourceAuthName + targetAuthName); auto hit = d->cacheAllowedAuthorities_.find(key); if (hit != d->cacheAllowedAuthorities_.end()) { return hit->second; } auto sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = ? AND target_auth_name = ?", {sourceAuthName, targetAuthName}); if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = ? AND target_auth_name = 'any'", {sourceAuthName}); } if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = 'any' AND target_auth_name = ?", {targetAuthName}); } if (sqlRes.empty()) { sqlRes = d->run( "SELECT allowed_authorities FROM authority_to_authority_preference " "WHERE source_auth_name = 'any' AND target_auth_name = 'any'", {}); } if (sqlRes.empty()) { d->cacheAllowedAuthorities_[key] = std::vector(); return std::vector(); } auto res = split(sqlRes.front()[0], ','); d->cacheAllowedAuthorities_[key] = res; return res; } // --------------------------------------------------------------------------- std::list> DatabaseContext::getNonDeprecated(const std::string &tableName, const std::string &authName, const std::string &code) const { auto sqlRes = d->run("SELECT replacement_auth_name, replacement_code, source " "FROM deprecation " "WHERE table_name = ? AND deprecated_auth_name = ? " "AND deprecated_code = ?", {tableName, authName, code}); std::list> res; for (const auto &row : sqlRes) { const auto &source = row[2]; if (source == "PROJ") { const auto &replacement_auth_name = row[0]; const auto &replacement_code = row[1]; res.emplace_back(replacement_auth_name, replacement_code); } } if (!res.empty()) { return res; } for (const auto &row : sqlRes) { const auto &replacement_auth_name = row[0]; const auto &replacement_code = row[1]; res.emplace_back(replacement_auth_name, replacement_code); } return res; } // --------------------------------------------------------------------------- std::vector DatabaseContext::getTransformationsForGridName( const DatabaseContextNNPtr &databaseContext, const std::string &gridName) { auto sqlRes = databaseContext->d->run( "SELECT auth_name, code FROM grid_transformation " "WHERE grid_name = ? OR grid_name = " "(SELECT original_grid_name FROM grid_alternatives " "WHERE proj_grid_name = ?)", {gridName, gridName}); std::vector res; for (const auto &row : sqlRes) { res.emplace_back(AuthorityFactory::create(databaseContext, row[0]) ->createCoordinateOperation(row[1], true)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct AuthorityFactory::Private { Private(const DatabaseContextNNPtr &contextIn, const std::string &authorityName) : context_(contextIn), authority_(authorityName) {} inline const std::string &authority() PROJ_PURE_DEFN { return authority_; } inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN { return context_; } // cppcheck-suppress functionStatic void setThis(AuthorityFactoryNNPtr factory) { thisFactory_ = factory.as_nullable(); } // cppcheck-suppress functionStatic AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); } inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) { if (auth_name == authority_) { return NN_NO_CHECK(thisFactory_.lock()); } return AuthorityFactory::create(context_, auth_name); } bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op, bool considerKnownGridsAsAvailable); UnitOfMeasure createUnitOfMeasure(const std::string &auth_name, const std::string &code); util::PropertyMap createProperties(const std::string &code, const std::string &name, bool deprecated, const std::vector &usages); util::PropertyMap createPropertiesSearchUsages(const std::string &table_name, const std::string &code, const std::string &name, bool deprecated); util::PropertyMap createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated, const std::string &remarks); SQLResultSet run(const std::string &sql, const ListOfParams ¶meters = ListOfParams()); SQLResultSet runWithCodeParam(const std::string &sql, const std::string &code); SQLResultSet runWithCodeParam(const char *sql, const std::string &code); bool hasAuthorityRestriction() const { return !authority_.empty() && authority_ != "any"; } SQLResultSet createProjectedCRSBegin(const std::string &code); crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code, const SQLResultSet &res); private: DatabaseContextNNPtr context_; std::string authority_; std::weak_ptr thisFactory_{}; }; // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::run(const std::string &sql, const ListOfParams ¶meters) { return context()->getPrivate()->run(sql, parameters); } // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::runWithCodeParam(const std::string &sql, const std::string &code) { return run(sql, {authority(), code}); } // --------------------------------------------------------------------------- SQLResultSet AuthorityFactory::Private::runWithCodeParam(const char *sql, const std::string &code) { return runWithCodeParam(std::string(sql), code); } // --------------------------------------------------------------------------- UnitOfMeasure AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name, const std::string &code) { return *(createFactory(auth_name)->createUnitOfMeasure(code)); } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createProperties( const std::string &code, const std::string &name, bool deprecated, const std::vector &usages) { auto props = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, authority()) .set(metadata::Identifier::CODE_KEY, code) .set(common::IdentifiedObject::NAME_KEY, name); if (deprecated) { props.set(common::IdentifiedObject::DEPRECATED_KEY, true); } if (!usages.empty()) { auto array(util::ArrayOfBaseObject::create()); for (const auto &usage : usages) { array->add(usage); } props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, util::nn_static_pointer_cast(array)); } return props; } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated) { const std::string sql( "SELECT extent.description, extent.south_lat, " "extent.north_lat, extent.west_lon, extent.east_lon, " "scope.scope, " "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) " "AS score " "FROM usage " "JOIN extent ON usage.extent_auth_name = extent.auth_name AND " "usage.extent_code = extent.code " "JOIN scope ON usage.scope_auth_name = scope.auth_name AND " "usage.scope_code = scope.code " "WHERE object_table_name = ? AND object_auth_name = ? AND " "object_code = ? " "ORDER BY score, usage.auth_name, usage.code"); auto res = run(sql, {table_name, authority(), code}); std::vector usages; for (const auto &row : res) { try { size_t idx = 0; const auto &extent_description = row[idx++]; const auto &south_lat_str = row[idx++]; const auto &north_lat_str = row[idx++]; const auto &west_lon_str = row[idx++]; const auto &east_lon_str = row[idx++]; const auto &scope = row[idx]; util::optional scopeOpt; if (!scope.empty()) { scopeOpt = scope; } metadata::ExtentPtr extent; if (south_lat_str.empty()) { extent = metadata::Extent::create( util::optional(extent_description), {}, {}, {}) .as_nullable(); } else { double south_lat = c_locale_stod(south_lat_str); double north_lat = c_locale_stod(north_lat_str); double west_lon = c_locale_stod(west_lon_str); double east_lon = c_locale_stod(east_lon_str); auto bbox = metadata::GeographicBoundingBox::create( west_lon, south_lat, east_lon, north_lat); extent = metadata::Extent::create( util::optional(extent_description), std::vector{bbox}, std::vector(), std::vector()) .as_nullable(); } usages.emplace_back(ObjectDomain::create(scopeOpt, extent)); } catch (const std::exception &) { } } return createProperties(code, name, deprecated, std::move(usages)); } // --------------------------------------------------------------------------- util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( const std::string &table_name, const std::string &code, const std::string &name, bool deprecated, const std::string &remarks) { auto props = createPropertiesSearchUsages(table_name, code, name, deprecated); if (!remarks.empty()) props.set(common::IdentifiedObject::REMARKS_KEY, remarks); return props; } // --------------------------------------------------------------------------- bool AuthorityFactory::Private::rejectOpDueToMissingGrid( const operation::CoordinateOperationNNPtr &op, bool considerKnownGridsAsAvailable) { for (const auto &gridDesc : op->gridsNeeded(context(), considerKnownGridsAsAvailable)) { if (!gridDesc.available) { return true; } } return false; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::~AuthorityFactory() = default; //! @endcond // --------------------------------------------------------------------------- AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context, const std::string &authorityName) : d(internal::make_unique(context, authorityName)) {} // --------------------------------------------------------------------------- // clang-format off /** \brief Instantiate a AuthorityFactory. * * The authority name might be set to the empty string in the particular case * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const * is called. * * @param context Contexte. * @param authorityName Authority name. * @return new AuthorityFactory. */ // clang-format on AuthorityFactoryNNPtr AuthorityFactory::create(const DatabaseContextNNPtr &context, const std::string &authorityName) { const auto getFactory = [&context, &authorityName]() { for (const auto &knownName : {"EPSG", "ESRI", "PROJ"}) { if (ci_equal(authorityName, knownName)) { return AuthorityFactory::nn_make_shared( context, knownName); } } return AuthorityFactory::nn_make_shared( context, authorityName); }; auto factory = getFactory(); factory->d->setThis(factory); return factory; } // --------------------------------------------------------------------------- /** \brief Returns the database context. */ const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const { return d->context(); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::CRSInfo::CRSInfo() : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{}, bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{}, north_lat_degree{}, areaName{}, projectionMethodName{} {} //! @endcond // --------------------------------------------------------------------------- /** \brief Returns an arbitrary object from a code. * * The returned object will typically be an instance of Datum, * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of * the object is know at compile time, it is recommended to invoke the most * precise method instead of this one (for example * createCoordinateReferenceSystem(code) instead of createObject(code) * if the caller know he is asking for a coordinate reference system). * * If there are several objects with the same code, a FactoryException is * thrown. * * @param code Object code allocated by authority. (e.g. "4326") * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ util::BaseObjectNNPtr AuthorityFactory::createObject(const std::string &code) const { auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view " "WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("not found", d->authority(), code); } if (res.size() != 1) { std::string msg( "More than one object matching specified code. Objects found in "); bool first = true; for (const auto &row : res) { if (!first) msg += ", "; msg += row[0]; first = false; } throw FactoryException(msg); } const auto &first_row = res.front(); const auto &table_name = first_row[0]; const auto &type = first_row[1]; if (table_name == "extent") { return util::nn_static_pointer_cast( createExtent(code)); } if (table_name == "unit_of_measure") { return util::nn_static_pointer_cast( createUnitOfMeasure(code)); } if (table_name == "prime_meridian") { return util::nn_static_pointer_cast( createPrimeMeridian(code)); } if (table_name == "ellipsoid") { return util::nn_static_pointer_cast( createEllipsoid(code)); } if (table_name == "geodetic_datum") { if (type == "ensemble") { return util::nn_static_pointer_cast( createDatumEnsemble(code, table_name)); } return util::nn_static_pointer_cast( createGeodeticDatum(code)); } if (table_name == "vertical_datum") { if (type == "ensemble") { return util::nn_static_pointer_cast( createDatumEnsemble(code, table_name)); } return util::nn_static_pointer_cast( createVerticalDatum(code)); } if (table_name == "geodetic_crs") { return util::nn_static_pointer_cast( createGeodeticCRS(code)); } if (table_name == "vertical_crs") { return util::nn_static_pointer_cast( createVerticalCRS(code)); } if (table_name == "projected_crs") { return util::nn_static_pointer_cast( createProjectedCRS(code)); } if (table_name == "compound_crs") { return util::nn_static_pointer_cast( createCompoundCRS(code)); } if (table_name == "conversion") { return util::nn_static_pointer_cast( createConversion(code)); } if (table_name == "helmert_transformation" || table_name == "grid_transformation" || table_name == "other_transformation" || table_name == "concatenated_operation") { return util::nn_static_pointer_cast( createCoordinateOperation(code, false)); } throw FactoryException("unimplemented factory for " + res.front()[0]); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static FactoryException buildFactoryException(const char *type, const std::string &code, const std::exception &ex) { return FactoryException(std::string("cannot build ") + type + " " + code + ": " + ex.what()); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a metadata::Extent from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ metadata::ExtentNNPtr AuthorityFactory::createExtent(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto extent = d->context()->d->getExtentFromCache(cacheKey); if (extent) { return NN_NO_CHECK(extent); } } auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, " "deprecated FROM extent WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(sql, code); if (res.empty()) { throw NoSuchAuthorityCodeException("extent not found", d->authority(), code); } try { const auto &row = res.front(); const auto &description = row[0]; if (row[1].empty()) { auto extent = metadata::Extent::create( util::optional(description), {}, {}, {}); d->context()->d->cache(cacheKey, extent); return extent; } double south_lat = c_locale_stod(row[1]); double north_lat = c_locale_stod(row[2]); double west_lon = c_locale_stod(row[3]); double east_lon = c_locale_stod(row[4]); auto bbox = metadata::GeographicBoundingBox::create( west_lon, south_lat, east_lon, north_lat); auto extent = metadata::Extent::create( util::optional(description), std::vector{bbox}, std::vector(), std::vector()); d->context()->d->cache(cacheKey, extent); return extent; } catch (const std::exception &ex) { throw buildFactoryException("extent", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a common::UnitOfMeasure from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ UnitOfMeasureNNPtr AuthorityFactory::createUnitOfMeasure(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto uom = d->context()->d->getUOMFromCache(cacheKey); if (uom) { return NN_NO_CHECK(uom); } } auto res = d->context()->d->run( "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE " "auth_name = ? AND code = ?", {d->authority(), code}, true); if (res.empty()) { throw NoSuchAuthorityCodeException("unit of measure not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = (row[0] == "degree (supplier to define representation)") ? UnitOfMeasure::DEGREE.name() : row[0]; double conv_factor = (code == "9107" || code == "9108") ? UnitOfMeasure::DEGREE.conversionToSI() : c_locale_stod(row[1]); constexpr double EPS = 1e-10; if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) < EPS * UnitOfMeasure::DEGREE.conversionToSI()) { conv_factor = UnitOfMeasure::DEGREE.conversionToSI(); } if (std::fabs(conv_factor - UnitOfMeasure::ARC_SECOND.conversionToSI()) < EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) { conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI(); } const auto &type_str = row[2]; UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN; if (type_str == "length") unitType = UnitOfMeasure::Type::LINEAR; else if (type_str == "angle") unitType = UnitOfMeasure::Type::ANGULAR; else if (type_str == "scale") unitType = UnitOfMeasure::Type::SCALE; else if (type_str == "time") unitType = UnitOfMeasure::Type::TIME; auto uom = util::nn_make_shared( name, conv_factor, unitType, d->authority(), code); d->context()->d->cache(cacheKey, uom); return uom; } catch (const std::exception &ex) { throw buildFactoryException("unit of measure", code, ex); } } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static double normalizeMeasure(const std::string &uom_code, const std::string &value, std::string &normalized_uom_code) { if (uom_code == "9110") // DDD.MMSSsss..... { double normalized_value = c_locale_stod(value); std::ostringstream buffer; buffer.imbue(std::locale::classic()); constexpr size_t precision = 12; buffer << std::fixed << std::setprecision(precision) << normalized_value; auto formatted = buffer.str(); size_t dotPos = formatted.find('.'); assert(dotPos + 1 + precision == formatted.size()); auto minutes = formatted.substr(dotPos + 1, 2); auto seconds = formatted.substr(dotPos + 3); assert(seconds.size() == precision - 2); normalized_value = (normalized_value < 0 ? -1.0 : 1.0) * (std::floor(std::fabs(normalized_value)) + c_locale_stod(minutes) / 60. + (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) / 3600.); normalized_uom_code = common::UnitOfMeasure::DEGREE.code(); /* coverity[overflow_sink] */ return normalized_value; } else { normalized_uom_code = uom_code; return c_locale_stod(value); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a datum::PrimeMeridian from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::PrimeMeridianNNPtr AuthorityFactory::createPrimeMeridian(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey); if (pm) { return NN_NO_CHECK(pm); } } auto res = d->runWithCodeParam( "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM " "prime_meridian WHERE " "auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("prime meridian not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &longitude = row[1]; const auto &uom_auth_name = row[2]; const auto &uom_code = row[3]; const bool deprecated = row[4] == "1"; std::string normalized_uom_code(uom_code); const double normalized_value = normalizeMeasure(uom_code, longitude, normalized_uom_code); auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code); auto props = d->createProperties(code, name, deprecated, {}); auto pm = datum::PrimeMeridian::create( props, common::Angle(normalized_value, uom)); d->context()->d->cache(cacheKey, pm); return pm; } catch (const std::exception &ex) { throw buildFactoryException("prime meridian", code, ex); } } // --------------------------------------------------------------------------- /** \brief Identify a celestial body from an approximate radius. * * @param semi_major_axis Approximate semi-major axis. * @param tolerance Relative error allowed. * @return celestial body name if one single match found. * @throw FactoryException */ std::string AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis, double tolerance) const { auto res = d->run("SELECT name, (ABS(semi_major_axis - ?) / semi_major_axis ) " "AS rel_error FROM celestial_body WHERE rel_error <= ?", {semi_major_axis, tolerance}); if (res.empty()) { throw FactoryException("no match found"); } if (res.size() > 1) { throw FactoryException("more than one match found"); } return res.front()[0]; } // --------------------------------------------------------------------------- /** \brief Returns a datum::Ellipsoid from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::EllipsoidNNPtr AuthorityFactory::createEllipsoid(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey); if (ellps) { return NN_NO_CHECK(ellps); } } auto res = d->runWithCodeParam( "SELECT ellipsoid.name, ellipsoid.semi_major_axis, " "ellipsoid.uom_auth_name, ellipsoid.uom_code, " "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, " "celestial_body.name AS body_name, ellipsoid.deprecated FROM " "ellipsoid JOIN celestial_body " "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND " "ellipsoid.celestial_body_code = celestial_body.code WHERE " "ellipsoid.auth_name = ? AND ellipsoid.code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("ellipsoid not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &semi_major_axis_str = row[1]; double semi_major_axis = c_locale_stod(semi_major_axis_str); const auto &uom_auth_name = row[2]; const auto &uom_code = row[3]; const auto &inv_flattening_str = row[4]; const auto &semi_minor_axis_str = row[5]; const auto &body = row[6]; const bool deprecated = row[7] == "1"; auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code); auto props = d->createProperties(code, name, deprecated, {}); if (!inv_flattening_str.empty()) { auto ellps = datum::Ellipsoid::createFlattenedSphere( props, common::Length(semi_major_axis, uom), common::Scale(c_locale_stod(inv_flattening_str)), body); d->context()->d->cache(cacheKey, ellps); return ellps; } else if (semi_major_axis_str == semi_minor_axis_str) { auto ellps = datum::Ellipsoid::createSphere( props, common::Length(semi_major_axis, uom), body); d->context()->d->cache(cacheKey, ellps); return ellps; } else { auto ellps = datum::Ellipsoid::createTwoAxis( props, common::Length(semi_major_axis, uom), common::Length(c_locale_stod(semi_minor_axis_str), uom), body); d->context()->d->cache(cacheKey, ellps); return ellps; } } catch (const std::exception &ex) { throw buildFactoryException("ellipsoid", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::GeodeticReferenceFrame from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::GeodeticReferenceFrameNNPtr AuthorityFactory::createGeodeticDatum(const std::string &code) const { datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = true; createGeodeticDatumOrEnsemble(code, datum, datumEnsemble, turnEnsembleAsDatum); return NN_NO_CHECK(datum); } // --------------------------------------------------------------------------- void AuthorityFactory::createGeodeticDatumOrEnsemble( const std::string &code, datum::GeodeticReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const { const auto cacheKey(d->authority() + code); { outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey); if (outDatumEnsemble) { if (!turnEnsembleAsDatum) return; outDatumEnsemble = nullptr; } outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey); if (outDatum) { return; } } auto res = d->runWithCodeParam("SELECT name, ellipsoid_auth_name, ellipsoid_code, " "prime_meridian_auth_name, prime_meridian_code, " "publication_date, frame_reference_epoch, " "ensemble_accuracy, deprecated FROM geodetic_datum " "WHERE " "auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("geodetic datum not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &ellipsoid_auth_name = row[1]; const auto &ellipsoid_code = row[2]; const auto &prime_meridian_auth_name = row[3]; const auto &prime_meridian_code = row[4]; const auto &publication_date = row[5]; const auto &frame_reference_epoch = row[6]; const auto &ensemble_accuracy = row[7]; const bool deprecated = row[8] == "1"; std::string massagedName = name; if (turnEnsembleAsDatum) { if (name == "World Geodetic System 1984 ensemble") { massagedName = "World Geodetic System 1984"; } else if (name == "European Terrestrial Reference System 1989 ensemble") { massagedName = "European Terrestrial Reference System 1989"; } } auto props = d->createPropertiesSearchUsages("geodetic_datum", code, massagedName, deprecated); if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " "geodetic_datum_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto datumEnsemble = datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensemble_accuracy)); d->context()->d->cache(cacheKey, datumEnsemble); outDatumEnsemble = datumEnsemble.as_nullable(); } else { auto ellipsoid = d->createFactory(ellipsoid_auth_name) ->createEllipsoid(ellipsoid_code); auto pm = d->createFactory(prime_meridian_auth_name) ->createPrimeMeridian(prime_meridian_code); auto anchor = util::optional(); if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } auto datum = frame_reference_epoch.empty() ? datum::GeodeticReferenceFrame::create( props, ellipsoid, anchor, pm) : util::nn_static_pointer_cast< datum::GeodeticReferenceFrame>( datum::DynamicGeodeticReferenceFrame::create( props, ellipsoid, anchor, pm, common::Measure( c_locale_stod(frame_reference_epoch), common::UnitOfMeasure::YEAR), util::optional())); d->context()->d->cache(cacheKey, datum); outDatum = datum.as_nullable(); } } catch (const std::exception &ex) { throw buildFactoryException("geodetic reference frame", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::VerticalReferenceFrame from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::VerticalReferenceFrameNNPtr AuthorityFactory::createVerticalDatum(const std::string &code) const { datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = true; createVerticalDatumOrEnsemble(code, datum, datumEnsemble, turnEnsembleAsDatum); return NN_NO_CHECK(datum); } // --------------------------------------------------------------------------- void AuthorityFactory::createVerticalDatumOrEnsemble( const std::string &code, datum::VerticalReferenceFramePtr &outDatum, datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const { auto res = d->runWithCodeParam("SELECT name, publication_date, " "frame_reference_epoch, ensemble_accuracy, " "deprecated FROM " "vertical_datum WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("vertical datum not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &publication_date = row[1]; const auto &frame_reference_epoch = row[2]; const auto &ensemble_accuracy = row[3]; const bool deprecated = row[4] == "1"; auto props = d->createPropertiesSearchUsages("vertical_datum", code, name, deprecated); if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " "vertical_datum_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto datumEnsemble = datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensemble_accuracy)); outDatumEnsemble = datumEnsemble.as_nullable(); } else { if (!publication_date.empty()) { props.set("PUBLICATION_DATE", publication_date); } if (d->authority() == "ESRI" && starts_with(code, "from_geogdatum_")) { props.set("VERT_DATUM_TYPE", "2002"); } auto anchor = util::optional(); if (frame_reference_epoch.empty()) { outDatum = datum::VerticalReferenceFrame::create(props, anchor) .as_nullable(); } else { outDatum = datum::DynamicVerticalReferenceFrame::create( props, anchor, util::optional(), common::Measure(c_locale_stod(frame_reference_epoch), common::UnitOfMeasure::YEAR), util::optional()) .as_nullable(); } } } catch (const std::exception &ex) { throw buildFactoryException("vertical reference frame", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a datum::DatumEnsemble from the specified code. * * @param code Object code allocated by authority. * @param type "geodetic_datum", "vertical_datum" or empty string if unknown * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::DatumEnsembleNNPtr AuthorityFactory::createDatumEnsemble(const std::string &code, const std::string &type) const { auto res = d->run( "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM " "geodetic_datum WHERE " "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL " "UNION ALL " "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM " "vertical_datum WHERE " "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL", {d->authority(), code, d->authority(), code}); if (res.empty()) { throw NoSuchAuthorityCodeException("datum ensemble not found", d->authority(), code); } for (const auto &row : res) { const std::string &gotType = row[0]; const std::string &name = row[1]; const std::string &ensembleAccuracy = row[2]; const bool deprecated = row[3] == "1"; if (type.empty() || type == gotType) { auto resMembers = d->run("SELECT member_auth_name, member_code FROM " + gotType + "_ensemble_member WHERE " "ensemble_auth_name = ? AND ensemble_code = ? " "ORDER BY sequence", {d->authority(), code}); std::vector members; for (const auto &memberRow : resMembers) { members.push_back( d->createFactory(memberRow[0])->createDatum(memberRow[1])); } auto props = d->createPropertiesSearchUsages(gotType, code, name, deprecated); return datum::DatumEnsemble::create( props, std::move(members), metadata::PositionalAccuracy::create(ensembleAccuracy)); } } throw NoSuchAuthorityCodeException("datum ensemble not found", d->authority(), code); } // --------------------------------------------------------------------------- /** \brief Returns a datum::Datum from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const { auto res = d->run("SELECT 'geodetic_datum' FROM geodetic_datum WHERE " "auth_name = ? AND code = ? " "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE " "auth_name = ? AND code = ?", {d->authority(), code, d->authority(), code}); if (res.empty()) { throw NoSuchAuthorityCodeException("datum not found", d->authority(), code); } if (res.front()[0] == "geodetic_datum") { return createGeodeticDatum(code); } return createVerticalDatum(code); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static cs::MeridianPtr createMeridian(const std::string &val) { try { const std::string degW(std::string("\xC2\xB0") + "W"); if (ends_with(val, degW)) { return cs::Meridian::create(common::Angle( -c_locale_stod(val.substr(0, val.size() - degW.size())))); } const std::string degE(std::string("\xC2\xB0") + "E"); if (ends_with(val, degE)) { return cs::Meridian::create(common::Angle( c_locale_stod(val.substr(0, val.size() - degE.size())))); } } catch (const std::exception &) { } return nullptr; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a cs::CoordinateSystem from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ cs::CoordinateSystemNNPtr AuthorityFactory::createCoordinateSystem(const std::string &code) const { const auto cacheKey(d->authority() + code); { auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey); if (cs) { return NN_NO_CHECK(cs); } } auto res = d->runWithCodeParam( "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, " "cs.type FROM " "axis LEFT JOIN coordinate_system cs ON " "axis.coordinate_system_auth_name = cs.auth_name AND " "axis.coordinate_system_code = cs.code WHERE " "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER " "BY coordinate_system_order", code); if (res.empty()) { throw NoSuchAuthorityCodeException("coordinate system not found", d->authority(), code); } const auto &csType = res.front()[5]; std::vector axisList; for (const auto &row : res) { const auto &name = row[0]; const auto &abbrev = row[1]; const auto &orientation = row[2]; const auto &uom_auth_name = row[3]; const auto &uom_code = row[4]; if (uom_auth_name.empty() && csType != "ordinal") { throw FactoryException("no unit of measure for an axis is only " "supported for ordinatal CS"); } auto uom = uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(uom_auth_name, uom_code); auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name); const cs::AxisDirection *direction = cs::AxisDirection::valueOf(orientation); cs::MeridianPtr meridian; if (direction == nullptr) { if (orientation == "Geocentre > equator/0" "\xC2\xB0" "E") { direction = &(cs::AxisDirection::GEOCENTRIC_X); } else if (orientation == "Geocentre > equator/90" "\xC2\xB0" "E") { direction = &(cs::AxisDirection::GEOCENTRIC_Y); } else if (orientation == "Geocentre > north pole") { direction = &(cs::AxisDirection::GEOCENTRIC_Z); } else if (starts_with(orientation, "North along ")) { direction = &(cs::AxisDirection::NORTH); meridian = createMeridian(orientation.substr(strlen("North along "))); } else if (starts_with(orientation, "South along ")) { direction = &(cs::AxisDirection::SOUTH); meridian = createMeridian(orientation.substr(strlen("South along "))); } else { throw FactoryException("unknown axis direction: " + orientation); } } axisList.emplace_back(cs::CoordinateSystemAxis::create( props, abbrev, *direction, uom, meridian)); } const auto cacheAndRet = [this, &cacheKey](const cs::CoordinateSystemNNPtr &cs) { d->context()->d->cache(cacheKey, cs); return cs; }; auto props = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, d->authority()) .set(metadata::Identifier::CODE_KEY, code); if (csType == "ellipsoidal") { if (axisList.size() == 2) { return cacheAndRet( cs::EllipsoidalCS::create(props, axisList[0], axisList[1])); } if (axisList.size() == 3) { return cacheAndRet(cs::EllipsoidalCS::create( props, axisList[0], axisList[1], axisList[2])); } throw FactoryException("invalid number of axis for EllipsoidalCS"); } if (csType == "Cartesian") { if (axisList.size() == 2) { return cacheAndRet( cs::CartesianCS::create(props, axisList[0], axisList[1])); } if (axisList.size() == 3) { return cacheAndRet(cs::CartesianCS::create( props, axisList[0], axisList[1], axisList[2])); } throw FactoryException("invalid number of axis for CartesianCS"); } if (csType == "vertical") { if (axisList.size() == 1) { return cacheAndRet(cs::VerticalCS::create(props, axisList[0])); } throw FactoryException("invalid number of axis for VerticalCS"); } if (csType == "ordinal") { return cacheAndRet(cs::OrdinalCS::create(props, axisList)); } throw FactoryException("unhandled coordinate system type: " + csType); } // --------------------------------------------------------------------------- /** \brief Returns a crs::GeodeticCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::GeodeticCRSNNPtr AuthorityFactory::createGeodeticCRS(const std::string &code) const { return createGeodeticCRS(code, false); } // --------------------------------------------------------------------------- /** \brief Returns a crs::GeographicCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::GeographicCRSNNPtr AuthorityFactory::createGeographicCRS(const std::string &code) const { return NN_NO_CHECK(util::nn_dynamic_pointer_cast( createGeodeticCRS(code, true))); } // --------------------------------------------------------------------------- static crs::GeodeticCRSNNPtr cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS, const util::PropertyMap &props) { auto cs = geodCRS->coordinateSystem(); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(cs); if (ellipsoidalCS) { return crs::GeographicCRS::create(props, geodCRS->datum(), geodCRS->datumEnsemble(), NN_NO_CHECK(ellipsoidalCS)); } auto geocentricCS = util::nn_dynamic_pointer_cast(cs); if (geocentricCS) { return crs::GeodeticCRS::create(props, geodCRS->datum(), geodCRS->datumEnsemble(), NN_NO_CHECK(geocentricCS)); } return geodCRS; } // --------------------------------------------------------------------------- crs::GeodeticCRSNNPtr AuthorityFactory::createGeodeticCRS(const std::string &code, bool geographicOnly) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto geogCRS = std::dynamic_pointer_cast(crs); if (geogCRS) { return NN_NO_CHECK(geogCRS); } throw NoSuchAuthorityCodeException("geodeticCRS not found", d->authority(), code); } std::string sql("SELECT name, type, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " "text_definition, " "deprecated FROM " "geodetic_crs WHERE auth_name = ? AND code = ?"); if (geographicOnly) { sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ")"; } auto res = d->runWithCodeParam(sql, code); if (res.empty()) { throw NoSuchAuthorityCodeException("geodeticCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &type = row[1]; const auto &cs_auth_name = row[2]; const auto &cs_code = row[3]; const auto &datum_auth_name = row[4]; const auto &datum_code = row[5]; const auto &text_definition = row[6]; const bool deprecated = row[7] == "1"; auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name, deprecated); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(d->context()); auto obj = createFromUserInput( pj_add_type_crs_if_needed(text_definition), d->context()); auto geodCRS = util::nn_dynamic_pointer_cast(obj); if (geodCRS) { auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto boundCRS = dynamic_cast(obj.get()); if (boundCRS) { geodCRS = util::nn_dynamic_pointer_cast( boundCRS->baseCRS()); if (geodCRS) { auto newBoundCRS = crs::BoundCRS::create( cloneWithProps(NN_NO_CHECK(geodCRS), props), boundCRS->hubCRS(), boundCRS->transformation()); return NN_NO_CHECK( util::nn_dynamic_pointer_cast( newBoundCRS->baseCRSWithCanonicalBoundCRS())); } } throw FactoryException( "text_definition does not define a GeodeticCRS"); } auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; d->createFactory(datum_auth_name) ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble, turnEnsembleAsDatum); auto ellipsoidalCS = util::nn_dynamic_pointer_cast(cs); if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) { auto crsRet = crs::GeographicCRS::create( props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } auto geocentricCS = util::nn_dynamic_pointer_cast(cs); if (type == GEOCENTRIC && geocentricCS) { auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(geocentricCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported (type, CS type) for geodeticCRS: " + type + ", " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("geodeticCRS", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::VerticalCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::VerticalCRSNNPtr AuthorityFactory::createVerticalCRS(const std::string &code) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto projCRS = std::dynamic_pointer_cast(crs); if (projCRS) { return NN_NO_CHECK(projCRS); } throw NoSuchAuthorityCodeException("verticalCRS not found", d->authority(), code); } auto res = d->runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, datum_auth_name, datum_code, " "deprecated FROM " "vertical_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("verticalCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &cs_auth_name = row[1]; const auto &cs_code = row[2]; const auto &datum_auth_name = row[3]; const auto &datum_code = row[4]; const bool deprecated = row[5] == "1"; auto cs = d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code); datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; d->createFactory(datum_auth_name) ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble, turnEnsembleAsDatum); auto props = d->createPropertiesSearchUsages("vertical_crs", code, name, deprecated); auto verticalCS = util::nn_dynamic_pointer_cast(cs); if (verticalCS) { auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble, NN_NO_CHECK(verticalCS)); d->context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported CS type for verticalCRS: " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("verticalCRS", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a operation::Conversion from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ operation::ConversionNNPtr AuthorityFactory::createConversion(const std::string &code) const { static const char *sql = "SELECT name, description, " "method_auth_name, method_code, method_name, " "param1_auth_name, param1_code, param1_name, param1_value, " "param1_uom_auth_name, param1_uom_code, " "param2_auth_name, param2_code, param2_name, param2_value, " "param2_uom_auth_name, param2_uom_code, " "param3_auth_name, param3_code, param3_name, param3_value, " "param3_uom_auth_name, param3_uom_code, " "param4_auth_name, param4_code, param4_name, param4_value, " "param4_uom_auth_name, param4_uom_code, " "param5_auth_name, param5_code, param5_name, param5_value, " "param5_uom_auth_name, param5_uom_code, " "param6_auth_name, param6_code, param6_name, param6_value, " "param6_uom_auth_name, param6_uom_code, " "param7_auth_name, param7_code, param7_name, param7_value, " "param7_uom_auth_name, param7_uom_code, " "deprecated FROM conversion WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(sql, code); if (res.empty()) { try { // Conversions using methods Change of Vertical Unit or // Height Depth Reversal are stored in other_transformation auto op = createCoordinateOperation( code, false /* allowConcatenated */, false /* usePROJAlternativeGridNames */, "other_transformation"); auto conv = util::nn_dynamic_pointer_cast(op); if (conv) { return NN_NO_CHECK(conv); } } catch (const std::exception &) { } throw NoSuchAuthorityCodeException("conversion not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const size_t base_param_idx = idx; std::vector parameters; std::vector values; constexpr int N_MAX_PARAMS = 7; for (int i = 0; i < N_MAX_PARAMS; ++i) { const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; if (param_auth_name.empty()) { break; } const auto ¶m_code = row[base_param_idx + i * 6 + 1]; const auto ¶m_name = row[base_param_idx + i * 6 + 2]; const auto ¶m_value = row[base_param_idx + i * 6 + 3]; const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) .set(metadata::Identifier::CODE_KEY, param_code) .set(common::IdentifiedObject::NAME_KEY, param_name))); std::string normalized_uom_code(param_uom_code); const double normalized_value = normalizeMeasure( param_uom_code, param_value, normalized_uom_code); auto uom = d->createUnitOfMeasure(param_uom_auth_name, normalized_uom_code); values.emplace_back(operation::ParameterValue::create( common::Measure(normalized_value, uom))); } const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1"; auto propConversion = d->createPropertiesSearchUsages( "conversion", code, name, deprecated); if (!description.empty()) propConversion.set(common::IdentifiedObject::REMARKS_KEY, description); auto propMethod = util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, method_name); if (!method_auth_name.empty()) { propMethod .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code); } return operation::Conversion::create(propConversion, propMethod, parameters, values); } catch (const std::exception &ex) { throw buildFactoryException("conversion", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::ProjectedCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::ProjectedCRSNNPtr AuthorityFactory::createProjectedCRS(const std::string &code) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { auto projCRS = std::dynamic_pointer_cast(crs); if (projCRS) { return NN_NO_CHECK(projCRS); } throw NoSuchAuthorityCodeException("projectedCRS not found", d->authority(), code); } return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code)); } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** Returns the result of the SQL query needed by createProjectedCRSEnd * * The split in two functions is for createFromCoordinateReferenceSystemCodes() * convenience, to avoid throwing exceptions. */ SQLResultSet AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) { return runWithCodeParam( "SELECT name, coordinate_system_auth_name, " "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, " "conversion_auth_name, conversion_code, " "text_definition, " "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?", code); } // --------------------------------------------------------------------------- /** Build a ProjectedCRS from the result of createProjectedCRSBegin() */ crs::ProjectedCRSNNPtr AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code, const SQLResultSet &res) { const auto cacheKey(authority() + code); if (res.empty()) { throw NoSuchAuthorityCodeException("projectedCRS not found", authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &cs_auth_name = row[1]; const auto &cs_code = row[2]; const auto &geodetic_crs_auth_name = row[3]; const auto &geodetic_crs_code = row[4]; const auto &conversion_auth_name = row[5]; const auto &conversion_code = row[6]; const auto &text_definition = row[7]; const bool deprecated = row[8] == "1"; auto props = createPropertiesSearchUsages("projected_crs", code, name, deprecated); if (!text_definition.empty()) { DatabaseContext::Private::RecursionDetector detector(context()); auto obj = createFromUserInput( pj_add_type_crs_if_needed(text_definition), context()); auto projCRS = dynamic_cast(obj.get()); if (projCRS) { const auto conv = projCRS->derivingConversion(); auto newConv = (conv->nameStr() == "unnamed") ? operation::Conversion::create( util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, name), conv->method(), conv->parameterValues()) : conv; auto crsRet = crs::ProjectedCRS::create( props, projCRS->baseCRS(), newConv, projCRS->coordinateSystem()); context()->d->cache(cacheKey, crsRet); return crsRet; } auto boundCRS = dynamic_cast(obj.get()); if (boundCRS) { projCRS = dynamic_cast( boundCRS->baseCRS().get()); if (projCRS) { auto newBoundCRS = crs::BoundCRS::create( crs::ProjectedCRS::create(props, projCRS->baseCRS(), projCRS->derivingConversion(), projCRS->coordinateSystem()), boundCRS->hubCRS(), boundCRS->transformation()); return NN_NO_CHECK( util::nn_dynamic_pointer_cast( newBoundCRS->baseCRSWithCanonicalBoundCRS())); } } throw FactoryException( "text_definition does not define a ProjectedCRS"); } auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code); auto baseCRS = createFactory(geodetic_crs_auth_name) ->createGeodeticCRS(geodetic_crs_code); auto conv = createFactory(conversion_auth_name) ->createConversion(conversion_code); if (conv->nameStr() == "unnamed") { conv = conv->shallowClone(); conv->setProperties(util::PropertyMap().set( common::IdentifiedObject::NAME_KEY, name)); } auto cartesianCS = util::nn_dynamic_pointer_cast(cs); if (cartesianCS) { auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv, NN_NO_CHECK(cartesianCS)); context()->d->cache(cacheKey, crsRet); return crsRet; } throw FactoryException("unsupported CS type for projectedCRS: " + cs->getWKT2Type(true)); } catch (const std::exception &ex) { throw buildFactoryException("projectedCRS", code, ex); } } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a crs::CompoundCRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::CompoundCRSNNPtr AuthorityFactory::createCompoundCRS(const std::string &code) const { auto res = d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, " "vertical_crs_auth_name, vertical_crs_code, " "deprecated FROM " "compound_crs WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("compoundCRS not found", d->authority(), code); } try { const auto &row = res.front(); const auto &name = row[0]; const auto &horiz_crs_auth_name = row[1]; const auto &horiz_crs_code = row[2]; const auto &vertical_crs_auth_name = row[3]; const auto &vertical_crs_code = row[4]; const bool deprecated = row[5] == "1"; auto horizCRS = d->createFactory(horiz_crs_auth_name) ->createCoordinateReferenceSystem(horiz_crs_code, false); auto vertCRS = d->createFactory(vertical_crs_auth_name) ->createVerticalCRS(vertical_crs_code); auto props = d->createPropertiesSearchUsages("compound_crs", code, name, deprecated); return crs::CompoundCRS::create( props, std::vector{horizCRS, vertCRS}); } catch (const std::exception &ex) { throw buildFactoryException("compoundCRS", code, ex); } } // --------------------------------------------------------------------------- /** \brief Returns a crs::CRS from the specified code. * * @param code Object code allocated by authority. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem( const std::string &code) const { return createCoordinateReferenceSystem(code, true); } //! @cond Doxygen_Suppress crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(const std::string &code, bool allowCompound) const { const auto cacheKey(d->authority() + code); auto crs = d->context()->d->getCRSFromCache(cacheKey); if (crs) { return NN_NO_CHECK(crs); } auto res = d->runWithCodeParam( "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("crs not found", d->authority(), code); } const auto &type = res.front()[0]; if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC) { return createGeodeticCRS(code); } if (type == VERTICAL) { return createVerticalCRS(code); } if (type == PROJECTED) { return createProjectedCRS(code); } if (allowCompound && type == COMPOUND) { return createCompoundCRS(code); } throw FactoryException("unhandled CRS type: " + type); } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { return util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, name) .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) .set(metadata::Identifier::CODE_KEY, code); } // --------------------------------------------------------------------------- static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) { const char *name = operation::OperationParameter::getNameForEPSGCode(code); assert(name); return operation::OperationParameter::create( createMapNameEPSGCode(name, code)); } static operation::ParameterValueNNPtr createLength(const std::string &value, const UnitOfMeasure &uom) { return operation::ParameterValue::create( common::Length(c_locale_stod(value), uom)); } static operation::ParameterValueNNPtr createAngle(const std::string &value, const UnitOfMeasure &uom) { return operation::ParameterValue::create( common::Angle(c_locale_stod(value), uom)); } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a operation::CoordinateOperation from the specified code. * * @param code Object code allocated by authority. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @return object. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const std::string &code, bool usePROJAlternativeGridNames) const { return createCoordinateOperation(code, true, usePROJAlternativeGridNames, std::string()); } operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const std::string &code, bool allowConcatenated, bool usePROJAlternativeGridNames, const std::string &typeIn) const { std::string type(typeIn); if (type.empty()) { auto res = d->runWithCodeParam( "SELECT type FROM coordinate_operation_with_conversion_view " "WHERE auth_name = ? AND code = ?", code); if (res.empty()) { throw NoSuchAuthorityCodeException("coordinate operation not found", d->authority(), code); } type = res.front()[0]; } if (type == "conversion") { return createConversion(code); } if (type == "helmert_transformation") { auto res = d->runWithCodeParam( "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "accuracy, tx, ty, tz, translation_uom_auth_name, " "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, " "rotation_uom_code, scale_difference, " "scale_difference_uom_auth_name, scale_difference_uom_code, " "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, " "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, " "rate_rotation_uom_auth_name, rate_rotation_uom_code, " "rate_scale_difference, rate_scale_difference_uom_auth_name, " "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, " "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, " "operation_version, deprecated FROM " "helmert_transformation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException( "helmert_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &tx = row[idx++]; const auto &ty = row[idx++]; const auto &tz = row[idx++]; const auto &translation_uom_auth_name = row[idx++]; const auto &translation_uom_code = row[idx++]; const auto &rx = row[idx++]; const auto &ry = row[idx++]; const auto &rz = row[idx++]; const auto &rotation_uom_auth_name = row[idx++]; const auto &rotation_uom_code = row[idx++]; const auto &scale_difference = row[idx++]; const auto &scale_difference_uom_auth_name = row[idx++]; const auto &scale_difference_uom_code = row[idx++]; const auto &rate_tx = row[idx++]; const auto &rate_ty = row[idx++]; const auto &rate_tz = row[idx++]; const auto &rate_translation_uom_auth_name = row[idx++]; const auto &rate_translation_uom_code = row[idx++]; const auto &rate_rx = row[idx++]; const auto &rate_ry = row[idx++]; const auto &rate_rz = row[idx++]; const auto &rate_rotation_uom_auth_name = row[idx++]; const auto &rate_rotation_uom_code = row[idx++]; const auto &rate_scale_difference = row[idx++]; const auto &rate_scale_difference_uom_auth_name = row[idx++]; const auto &rate_scale_difference_uom_code = row[idx++]; const auto &epoch = row[idx++]; const auto &epoch_uom_auth_name = row[idx++]; const auto &epoch_uom_code = row[idx++]; const auto &px = row[idx++]; const auto &py = row[idx++]; const auto &pz = row[idx++]; const auto &pivot_uom_auth_name = row[idx++]; const auto &pivot_uom_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; assert(idx == row.size()); auto uom_translation = d->createUnitOfMeasure( translation_uom_auth_name, translation_uom_code); auto uom_epoch = epoch_uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(epoch_uom_auth_name, epoch_uom_code); auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); std::vector parameters; std::vector values; parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION)); values.emplace_back(createLength(tx, uom_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION)); values.emplace_back(createLength(ty, uom_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION)); values.emplace_back(createLength(tz, uom_translation)); if (!rx.empty()) { // Helmert 7-, 8-, 10- or 15- parameter cases auto uom_rotation = d->createUnitOfMeasure( rotation_uom_auth_name, rotation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_X_AXIS_ROTATION)); values.emplace_back(createAngle(rx, uom_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Y_AXIS_ROTATION)); values.emplace_back(createAngle(ry, uom_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_Z_AXIS_ROTATION)); values.emplace_back(createAngle(rz, uom_rotation)); auto uom_scale_difference = scale_difference_uom_auth_name.empty() ? common::UnitOfMeasure::NONE : d->createUnitOfMeasure(scale_difference_uom_auth_name, scale_difference_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_SCALE_DIFFERENCE)); values.emplace_back(operation::ParameterValue::create( common::Scale(c_locale_stod(scale_difference), uom_scale_difference))); } if (!rate_tx.empty()) { // Helmert 15-parameter auto uom_rate_translation = d->createUnitOfMeasure( rate_translation_uom_auth_name, rate_translation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_tx, uom_rate_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_ty, uom_rate_translation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION)); values.emplace_back( createLength(rate_tz, uom_rate_translation)); auto uom_rate_rotation = d->createUnitOfMeasure( rate_rotation_uom_auth_name, rate_rotation_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION)); values.emplace_back(createAngle(rate_rx, uom_rate_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION)); values.emplace_back(createAngle(rate_ry, uom_rate_rotation)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION)); values.emplace_back(createAngle(rate_rz, uom_rate_rotation)); auto uom_rate_scale_difference = d->createUnitOfMeasure(rate_scale_difference_uom_auth_name, rate_scale_difference_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE)); values.emplace_back(operation::ParameterValue::create( common::Scale(c_locale_stod(rate_scale_difference), uom_rate_scale_difference))); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_REFERENCE_EPOCH)); values.emplace_back(operation::ParameterValue::create( common::Measure(c_locale_stod(epoch), uom_epoch))); } else if (uom_epoch != common::UnitOfMeasure::NONE) { // Helmert 8-parameter parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH)); values.emplace_back(operation::ParameterValue::create( common::Measure(c_locale_stod(epoch), uom_epoch))); } else if (!px.empty()) { // Molodensky-Badekas case auto uom_pivot = d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT)); values.emplace_back(createLength(px, uom_pivot)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT)); values.emplace_back(createLength(py, uom_pivot)); parameters.emplace_back(createOpParamNameEPSGCode( EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT)); values.emplace_back(createLength(pz, uom_pivot)); } auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); std::vector accuracies; if (!accuracy.empty()) { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } return operation::Transformation::create( props, sourceCRS, targetCRS, nullptr, propsMethod, parameters, values, accuracies); } catch (const std::exception &ex) { throw buildFactoryException("transformation", code, ex); } } if (type == "grid_transformation") { auto res = d->runWithCodeParam( "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, " "grid_name, " "grid2_param_auth_name, grid2_param_code, grid2_param_name, " "grid2_name, " "interpolation_crs_auth_name, interpolation_crs_code, " "operation_version, deprecated FROM " "grid_transformation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException("grid_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &grid_param_auth_name = row[idx++]; const auto &grid_param_code = row[idx++]; const auto &grid_param_name = row[idx++]; const auto &grid_name = row[idx++]; const auto &grid2_param_auth_name = row[idx++]; const auto &grid2_param_code = row[idx++]; const auto &grid2_param_name = row[idx++]; const auto &grid2_name = row[idx++]; const auto &interpolation_crs_auth_name = row[idx++]; const auto &interpolation_crs_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; assert(idx == row.size()); auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); auto interpolationCRS = interpolation_crs_auth_name.empty() ? nullptr : d->createFactory(interpolation_crs_auth_name) ->createCoordinateReferenceSystem( interpolation_crs_code) .as_nullable(); std::vector parameters; std::vector values; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, grid_param_name) .set(metadata::Identifier::CODESPACE_KEY, grid_param_auth_name) .set(metadata::Identifier::CODE_KEY, grid_param_code))); values.emplace_back( operation::ParameterValue::createFilename(grid_name)); if (!grid2_name.empty()) { parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(common::IdentifiedObject::NAME_KEY, grid2_param_name) .set(metadata::Identifier::CODESPACE_KEY, grid2_param_auth_name) .set(metadata::Identifier::CODE_KEY, grid2_param_code))); values.emplace_back( operation::ParameterValue::createFilename(grid2_name)); } auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); std::vector accuracies; if (!accuracy.empty()) { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } auto transf = operation::Transformation::create( props, sourceCRS, targetCRS, interpolationCRS, propsMethod, parameters, values, accuracies); if (usePROJAlternativeGridNames) { return transf->substitutePROJAlternativeGridNames(d->context()); } return transf; } catch (const std::exception &ex) { throw buildFactoryException("transformation", code, ex); } } if (type == "other_transformation") { std::ostringstream buffer; buffer.imbue(std::locale::classic()); buffer << "SELECT name, description, " "method_auth_name, method_code, method_name, " "source_crs_auth_name, source_crs_code, target_crs_auth_name, " "target_crs_code, " "interpolation_crs_auth_name, interpolation_crs_code, " "operation_version, accuracy, deprecated"; constexpr int N_MAX_PARAMS = 7; for (int i = 1; i <= N_MAX_PARAMS; ++i) { buffer << ", param" << i << "_auth_name"; buffer << ", param" << i << "_code"; buffer << ", param" << i << "_name"; buffer << ", param" << i << "_value"; buffer << ", param" << i << "_uom_auth_name"; buffer << ", param" << i << "_uom_code"; } buffer << " FROM other_transformation " "WHERE auth_name = ? AND code = ?"; auto res = d->runWithCodeParam(buffer.str(), code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException("other_transformation not found", d->authority(), code); } try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &method_auth_name = row[idx++]; const auto &method_code = row[idx++]; const auto &method_name = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &interpolation_crs_auth_name = row[idx++]; const auto &interpolation_crs_code = row[idx++]; const auto &operation_version = row[idx++]; const auto &accuracy = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; const size_t base_param_idx = idx; std::vector parameters; std::vector values; for (int i = 0; i < N_MAX_PARAMS; ++i) { const auto ¶m_auth_name = row[base_param_idx + i * 6 + 0]; if (param_auth_name.empty()) { break; } const auto ¶m_code = row[base_param_idx + i * 6 + 1]; const auto ¶m_name = row[base_param_idx + i * 6 + 2]; const auto ¶m_value = row[base_param_idx + i * 6 + 3]; const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4]; const auto ¶m_uom_code = row[base_param_idx + i * 6 + 5]; parameters.emplace_back(operation::OperationParameter::create( util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, param_auth_name) .set(metadata::Identifier::CODE_KEY, param_code) .set(common::IdentifiedObject::NAME_KEY, param_name))); std::string normalized_uom_code(param_uom_code); const double normalized_value = normalizeMeasure( param_uom_code, param_value, normalized_uom_code); auto uom = d->createUnitOfMeasure(param_uom_auth_name, normalized_uom_code); values.emplace_back(operation::ParameterValue::create( common::Measure(normalized_value, uom))); } idx = base_param_idx + 6 * N_MAX_PARAMS; (void)idx; assert(idx == row.size()); auto sourceCRS = d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code); auto targetCRS = d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code); auto interpolationCRS = interpolation_crs_auth_name.empty() ? nullptr : d->createFactory(interpolation_crs_auth_name) ->createCoordinateReferenceSystem( interpolation_crs_code) .as_nullable(); auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } std::vector accuracies; if (!accuracy.empty()) { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } if (method_auth_name == "PROJ") { if (method_code == "PROJString") { auto op = operation::SingleOperation::createPROJBased( props, method_name, sourceCRS, targetCRS, accuracies); op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return op; } else if (method_code == "WKT") { auto op = util::nn_dynamic_pointer_cast< operation::CoordinateOperation>( WKTParser().createFromWKT(method_name)); if (!op) { throw FactoryException("WKT string does not express a " "coordinate operation"); } op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return NN_NO_CHECK(op); } } auto propsMethod = util::PropertyMap() .set(metadata::Identifier::CODESPACE_KEY, method_auth_name) .set(metadata::Identifier::CODE_KEY, method_code) .set(common::IdentifiedObject::NAME_KEY, method_name); if (method_auth_name == metadata::Identifier::EPSG) { int method_code_int = std::atoi(method_code.c_str()); if (operation::isAxisOrderReversal(method_code_int) || method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT || method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { auto op = operation::Conversion::create(props, propsMethod, parameters, values); op->setCRSs(sourceCRS, targetCRS, interpolationCRS); return op; } } return operation::Transformation::create( props, sourceCRS, targetCRS, interpolationCRS, propsMethod, parameters, values, accuracies); } catch (const std::exception &ex) { throw buildFactoryException("transformation", code, ex); } } if (allowConcatenated && type == "concatenated_operation") { auto res = d->runWithCodeParam( "SELECT name, description, " "source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "accuracy, " "operation_version, deprecated FROM " "concatenated_operation WHERE auth_name = ? AND code = ?", code); if (res.empty()) { // shouldn't happen if foreign keys are OK throw NoSuchAuthorityCodeException( "concatenated_operation not found", d->authority(), code); } auto resSteps = d->runWithCodeParam( "SELECT step_auth_name, step_code FROM " "concatenated_operation_step WHERE operation_auth_name = ? " "AND operation_code = ? ORDER BY step_number", code); try { const auto &row = res.front(); size_t idx = 0; const auto &name = row[idx++]; const auto &description = row[idx++]; const auto &source_crs_auth_name = row[idx++]; const auto &source_crs_code = row[idx++]; const auto &target_crs_auth_name = row[idx++]; const auto &target_crs_code = row[idx++]; const auto &accuracy = row[idx++]; const auto &operation_version = row[idx++]; const auto &deprecated_str = row[idx++]; const bool deprecated = deprecated_str == "1"; std::vector operations; for (const auto &rowStep : resSteps) { const auto &step_auth_name = rowStep[0]; const auto &step_code = rowStep[1]; operations.push_back( d->createFactory(step_auth_name) ->createCoordinateOperation(step_code, false, usePROJAlternativeGridNames, std::string())); } operation::ConcatenatedOperation::fixStepsDirection( d->createFactory(source_crs_auth_name) ->createCoordinateReferenceSystem(source_crs_code), d->createFactory(target_crs_auth_name) ->createCoordinateReferenceSystem(target_crs_code), operations); auto props = d->createPropertiesSearchUsages( type, code, name, deprecated, description); if (!operation_version.empty()) { props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY, operation_version); } std::vector accuracies; if (!accuracy.empty()) { accuracies.emplace_back( metadata::PositionalAccuracy::create(accuracy)); } else { // Try to compute a reasonable accuracy from the members double totalAcc = -1; try { for (const auto &op : operations) { auto accs = op->coordinateOperationAccuracies(); if (accs.size() == 1) { double acc = c_locale_stod(accs[0]->value()); if (totalAcc < 0) { totalAcc = acc; } else { totalAcc += acc; } } else if (dynamic_cast( op.get())) { // A conversion is perfectly accurate. if (totalAcc < 0) { totalAcc = 0; } } else { totalAcc = -1; break; } } if (totalAcc >= 0) { accuracies.emplace_back( metadata::PositionalAccuracy::create( toString(totalAcc))); } } catch (const std::exception &) { } } return operation::ConcatenatedOperation::create(props, operations, accuracies); } catch (const std::exception &ex) { throw buildFactoryException("transformation", code, ex); } } throw FactoryException("unhandled coordinate operation type: " + type); } // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS. * * The list is ordered with preferred operations first. No attempt is made * at inferring operations that are not explicitly in the database. * * Deprecated operations are rejected. * * @param sourceCRSCode Source CRS code allocated by authority. * @param targetCRSCode Source CRS code allocated by authority. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException * @throw FactoryException */ std::vector AuthorityFactory::createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSCode, const std::string &targetCRSCode) const { return createFromCoordinateReferenceSystemCodes( d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false, false, false, false); } // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS. * * The list is ordered with preferred operations first. No attempt is made * at inferring operations that are not explicitly in the database (see * createFromCRSCodesWithIntermediates() for that), and only * source -> target operations are searched (ie if target -> source is present, * you need to call this method with the arguments reversed, and apply the * reverse transformations). * * Deprecated operations are rejected. * * If getAuthority() returns empty, then coordinate operations from all * authorities are considered. * * @param sourceCRSAuthName Authority name of sourceCRSCode * @param sourceCRSCode Source CRS code allocated by authority * sourceCRSAuthName. * @param targetCRSAuthName Authority name of targetCRSCode * @param targetCRSCode Source CRS code allocated by authority * targetCRSAuthName. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @param discardIfMissingGrid Whether coordinate operations that reference * missing grids should be removed from the result set. * @param considerKnownGridsAsAvailable Whether known grids should be considered * as available (typically when network is enabled). * @param discardSuperseded Whether cordinate operations that are superseded * (but not deprecated) should be removed from the result set. * @param tryReverseOrder whether to search in the reverse order too (and thus * inverse results found that way) * @param reportOnlyIntersectingTransformations if intersectingExtent1 and * intersectingExtent2 should be honored in a strict way. * @param intersectingExtent1 Optional extent that the resulting operations * must intersect. * @param intersectingExtent2 Optional extent that the resulting operations * must intersect. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException * @throw FactoryException */ std::vector AuthorityFactory::createFromCoordinateReferenceSystemCodes( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, bool tryReverseOrder, bool reportOnlyIntersectingTransformations, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { auto cacheKey(d->authority()); cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName; cacheKey += sourceCRSCode; cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName; cacheKey += targetCRSCode; cacheKey += (usePROJAlternativeGridNames ? '1' : '0'); cacheKey += (discardIfMissingGrid ? '1' : '0'); cacheKey += (considerKnownGridsAsAvailable ? '1' : '0'); cacheKey += (discardSuperseded ? '1' : '0'); cacheKey += (tryReverseOrder ? '1' : '0'); cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0'); for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { cacheKey += toString(bbox->southBoundLatitude()); cacheKey += toString(bbox->westBoundLongitude()); cacheKey += toString(bbox->northBoundLatitude()); cacheKey += toString(bbox->eastBoundLongitude()); } } } } std::vector list; if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) { return list; } // Check if sourceCRS would be the base of a ProjectedCRS targetCRS // In which case use the conversion of the ProjectedCRS if (!targetCRSAuthName.empty()) { auto targetFactory = d->createFactory(targetCRSAuthName); const auto cacheKeyProjectedCRS(targetFactory->d->authority() + targetCRSCode); auto crs = targetFactory->d->context()->d->getCRSFromCache( cacheKeyProjectedCRS); crs::ProjectedCRSPtr targetProjCRS; if (crs) { targetProjCRS = std::dynamic_pointer_cast(crs); } else { const auto sqlRes = targetFactory->d->createProjectedCRSBegin(targetCRSCode); if (!sqlRes.empty()) { try { targetProjCRS = targetFactory->d ->createProjectedCRSEnd(targetCRSCode, sqlRes) .as_nullable(); } catch (const std::exception &) { } } } if (targetProjCRS) { const auto &baseIds = targetProjCRS->baseCRS()->identifiers(); if (sourceCRSAuthName.empty() || (!baseIds.empty() && *(baseIds.front()->codeSpace()) == sourceCRSAuthName && baseIds.front()->code() == sourceCRSCode)) { bool ok = true; auto conv = targetProjCRS->derivingConversion(); if (d->hasAuthorityRestriction()) { ok = *(conv->identifiers().front()->codeSpace()) == d->authority(); } if (ok) { list.emplace_back(conv); d->context()->d->cache(cacheKey, list); return list; } } } } std::string sql; if (discardSuperseded) { sql = "SELECT source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " "extent.south_lat, extent.west_lon, extent.north_lat, " "extent.east_lon, " "ss.replacement_auth_name, ss.replacement_code FROM " "coordinate_operation_view cov " "JOIN usage ON " "usage.object_table_name = cov.table_name AND " "usage.object_auth_name = cov.auth_name AND " "usage.object_code = cov.code " "JOIN extent " "ON extent.auth_name = usage.extent_auth_name AND " "extent.code = usage.extent_code " "LEFT JOIN supersession ss ON " "ss.superseded_table_name = cov.table_name AND " "ss.superseded_auth_name = cov.auth_name AND " "ss.superseded_code = cov.code AND " "ss.superseded_table_name = ss.replacement_table_name AND " "ss.same_source_target_crs = 1 " "WHERE "; } else { sql = "SELECT source_crs_auth_name, source_crs_code, " "target_crs_auth_name, target_crs_code, " "cov.auth_name, cov.code, cov.table_name, " "extent.south_lat, extent.west_lon, extent.north_lat, " "extent.east_lon " "FROM " "coordinate_operation_view cov " "JOIN usage ON " "usage.object_table_name = cov.table_name AND " "usage.object_auth_name = cov.auth_name AND " "usage.object_code = cov.code " "JOIN extent " "ON extent.auth_name = usage.extent_auth_name AND " "extent.code = usage.extent_code " "WHERE "; } ListOfParams params; if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((source_crs_auth_name = ? AND source_crs_code = ? AND " "target_crs_auth_name = ? AND target_crs_code = ?) OR " "(source_crs_auth_name = ? AND source_crs_code = ? AND " "target_crs_auth_name = ? AND target_crs_code = ?)) AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } else { sql += "source_crs_auth_name = ? AND source_crs_code = ? AND " "target_crs_auth_name = ? AND target_crs_code = ? AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } } else if (!sourceCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((source_crs_auth_name = ? AND source_crs_code = ?) OR " "(target_crs_auth_name = ? AND target_crs_code = ?)) AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } else { sql += "source_crs_auth_name = ? AND source_crs_code = ? AND "; params.emplace_back(sourceCRSAuthName); params.emplace_back(sourceCRSCode); } } else if (!targetCRSAuthName.empty()) { if (tryReverseOrder) { sql += "((source_crs_auth_name = ? AND source_crs_code = ?) OR " "(target_crs_auth_name = ? AND target_crs_code = ?)) AND "; params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } else { sql += "target_crs_auth_name = ? AND target_crs_code = ? AND "; params.emplace_back(targetCRSAuthName); params.emplace_back(targetCRSCode); } } sql += "cov.deprecated = 0"; if (d->hasAuthorityRestriction()) { sql += " AND cov.auth_name = ?"; params.emplace_back(d->authority()); } sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, " "east_lon) DESC, " "(CASE WHEN accuracy is NULL THEN 1 ELSE 0 END), accuracy"; auto res = d->run(sql, params); std::set> setTransf; if (discardSuperseded) { for (const auto &row : res) { const auto &auth_name = row[4]; const auto &code = row[5]; setTransf.insert( std::pair(auth_name, code)); } } // Do a pass to determine if there are transformations that intersect // intersectingExtent1 & intersectingExtent2 std::vector intersectingTransformations; intersectingTransformations.resize(res.size()); bool hasIntersectingTransformations = false; size_t i = 0; for (const auto &row : res) { size_t thisI = i; ++i; if (discardSuperseded) { const auto &replacement_auth_name = row[11]; const auto &replacement_code = row[12]; if (!replacement_auth_name.empty() && setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } } bool intersecting = true; try { double south_lat = c_locale_stod(row[7]); double west_lon = c_locale_stod(row[8]); double north_lat = c_locale_stod(row[9]); double east_lon = c_locale_stod(row[10]); auto transf_extent = metadata::Extent::createFromBBOX( west_lon, south_lat, east_lon, north_lat); for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { if (!transf_extent->intersects(NN_NO_CHECK(extent))) { intersecting = false; break; } } } } catch (const std::exception &) { } intersectingTransformations[thisI] = intersecting; if (intersecting) hasIntersectingTransformations = true; } // If there are intersecting transformations, then only report those ones // If there are no intersecting transformations, report all of them // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we // still want to be able to use the Pulkovo datum shift if EPSG:32631 // coordinates are used i = 0; for (const auto &row : res) { size_t thisI = i; ++i; if ((hasIntersectingTransformations || reportOnlyIntersectingTransformations) && !intersectingTransformations[thisI]) { continue; } if (discardSuperseded) { const auto &replacement_auth_name = row[11]; const auto &replacement_code = row[12]; if (!replacement_auth_name.empty() && setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } } const auto &source_crs_auth_name = row[0]; const auto &source_crs_code = row[1]; const auto &target_crs_auth_name = row[2]; const auto &target_crs_code = row[3]; const auto &auth_name = row[4]; const auto &code = row[5]; const auto &table_name = row[6]; auto op = d->createFactory(auth_name)->createCoordinateOperation( code, true, usePROJAlternativeGridNames, table_name); if (tryReverseOrder && (!sourceCRSAuthName.empty() ? (source_crs_auth_name != sourceCRSAuthName || source_crs_code != sourceCRSCode) : (target_crs_auth_name != targetCRSAuthName || target_crs_code != targetCRSCode))) { op = op->inverse(); } if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } d->context()->d->cache(cacheKey, list); return list; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op, const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode) { auto concat = dynamic_cast(op.get()); if (!concat) { return false; } auto ops = concat->operations(); for (size_t i = 0; i + 1 < ops.size(); i++) { auto targetCRS = ops[i]->targetCRS(); if (targetCRS) { const auto &ids = targetCRS->identifiers(); if (ids.size() == 1 && ((*ids[0]->codeSpace() == sourceCRSAuthName && ids[0]->code() == sourceCRSCode) || (*ids[0]->codeSpace() == targetCRSAuthName && ids[0]->code() == targetCRSCode))) { return true; } } } return false; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns a list operation::CoordinateOperation between two CRS, * using intermediate codes. * * The list is ordered with preferred operations first. * * Deprecated operations are rejected. * * The method will take care of considering all potential combinations (ie * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to * call it with sourceCRS and targetCRS switched) * * If getAuthority() returns empty, then coordinate operations from all * authorities are considered. * * @param sourceCRSAuthName Authority name of sourceCRSCode * @param sourceCRSCode Source CRS code allocated by authority * sourceCRSAuthName. * @param targetCRSAuthName Authority name of targetCRSCode * @param targetCRSCode Source CRS code allocated by authority * targetCRSAuthName. * @param usePROJAlternativeGridNames Whether PROJ alternative grid names * should be substituted to the official grid names. * @param discardIfMissingGrid Whether coordinate operations that reference * missing grids should be removed from the result set. * @param considerKnownGridsAsAvailable Whether known grids should be considered * as available (typically when network is enabled). * @param discardSuperseded Whether cordinate operations that are superseded * (but not deprecated) should be removed from the result set. * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be * used as potential intermediate CRS. If the list is empty, the database will * be used to find common CRS in operations involving both the source and * target CRS. * @param allowedIntermediateObjectType Restrict the type of the intermediate * object considered. * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently * @param allowedAuthorities One or several authority name allowed for the two * coordinate operations that are going to be searched. When this vector is * no empty, it overrides the authority of this object. This is useful for * example when the coordinate operations to chain belong to two different * allowed authorities. * @param intersectingExtent1 Optional extent that the resulting operations * must intersect. * @param intersectingExtent2 Optional extent that the resulting operations * must intersect. * @return list of coordinate operations * @throw NoSuchAuthorityCodeException * @throw FactoryException */ std::vector AuthorityFactory::createFromCRSCodesWithIntermediates( const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector> &intermediateCRSAuthCodes, ObjectType allowedIntermediateObjectType, const std::vector &allowedAuthorities, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { std::vector listTmp; if (sourceCRSAuthName == targetCRSAuthName && sourceCRSCode == targetCRSCode) { return listTmp; } const std::string sqlProlog( discardSuperseded ? "SELECT v1.table_name as table1, " "v1.auth_name AS auth_name1, v1.code AS code1, " "v1.accuracy AS accuracy1, " "v2.table_name as table2, " "v2.auth_name AS auth_name2, v2.code AS code2, " "v2.accuracy as accuracy2, " "a1.south_lat AS south_lat1, " "a1.west_lon AS west_lon1, " "a1.north_lat AS north_lat1, " "a1.east_lon AS east_lon1, " "a2.south_lat AS south_lat2, " "a2.west_lon AS west_lon2, " "a2.north_lat AS north_lat2, " "a2.east_lon AS east_lon2, " "ss1.replacement_auth_name AS replacement_auth_name1, " "ss1.replacement_code AS replacement_code1, " "ss2.replacement_auth_name AS replacement_auth_name2, " "ss2.replacement_code AS replacement_code2 " "FROM coordinate_operation_view v1 " "JOIN coordinate_operation_view v2 " : "SELECT v1.table_name as table1, " "v1.auth_name AS auth_name1, v1.code AS code1, " "v1.accuracy AS accuracy1, " "v2.table_name as table2, " "v2.auth_name AS auth_name2, v2.code AS code2, " "v2.accuracy as accuracy2, " "a1.south_lat AS south_lat1, " "a1.west_lon AS west_lon1, " "a1.north_lat AS north_lat1, " "a1.east_lon AS east_lon1, " "a2.south_lat AS south_lat2, " "a2.west_lon AS west_lon2, " "a2.north_lat AS north_lat2, " "a2.east_lon AS east_lon2 " "FROM coordinate_operation_view v1 " "JOIN coordinate_operation_view v2 "); const std::string joinSupersession( "LEFT JOIN supersession ss1 ON " "ss1.superseded_table_name = v1.table_name AND " "ss1.superseded_auth_name = v1.auth_name AND " "ss1.superseded_code = v1.code AND " "ss1.superseded_table_name = ss1.replacement_table_name AND " "ss1.same_source_target_crs = 1 " "LEFT JOIN supersession ss2 ON " "ss2.superseded_table_name = v2.table_name AND " "ss2.superseded_auth_name = v2.auth_name AND " "ss2.superseded_code = v2.code AND " "ss2.superseded_table_name = ss2.replacement_table_name AND " "ss2.same_source_target_crs = 1 "); const std::string joinArea( (discardSuperseded ? joinSupersession : std::string()) + "JOIN usage u1 ON " "u1.object_table_name = v1.table_name AND " "u1.object_auth_name = v1.auth_name AND " "u1.object_code = v1.code " "JOIN extent a1 " "ON a1.auth_name = u1.extent_auth_name AND " "a1.code = u1.extent_code " "JOIN usage u2 ON " "u2.object_table_name = v2.table_name AND " "u2.object_auth_name = v2.auth_name AND " "u2.object_code = v2.code " "JOIN extent a2 " "ON a2.auth_name = u2.extent_auth_name AND " "a2.code = u2.extent_code "); const std::string orderBy( "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + " "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), " "accuracy1 + accuracy2"); // Case (source->intermediate) and (intermediate->target) std::string sql( sqlProlog + "ON v1.target_crs_auth_name = v2.source_crs_auth_name " "AND v1.target_crs_code = v2.source_crs_code " + joinArea + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "); std::string minDate; std::string criterionOnIntermediateCRS; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { auto sourceCRS = d->createFactory(sourceCRSAuthName) ->createGeodeticCRS(sourceCRSCode); auto targetCRS = d->createFactory(targetCRSAuthName) ->createGeodeticCRS(targetCRSCode); const auto &sourceDatum = sourceCRS->datum(); const auto &targetDatum = targetCRS->datum(); if (sourceDatum && sourceDatum->publicationDate().has_value() && targetDatum && targetDatum->publicationDate().has_value()) { const auto sourceDate(sourceDatum->publicationDate()->toString()); const auto targetDate(targetDatum->publicationDate()->toString()); minDate = std::min(sourceDate, targetDate); // Check that the datum of the intermediateCRS has a publication // date most recent that the one of the source and the target CRS // Except when using the usual WGS84 pivot which happens to have a // NULL publication date. criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x " "JOIN geodetic_datum y " "ON " "y.auth_name = x.datum_auth_name AND " "y.code = x.datum_code " "WHERE " "x.auth_name = v1.target_crs_auth_name AND " "x.code = v1.target_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D') AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } else { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE " "x.auth_name = v1.target_crs_auth_name AND " "x.code = v1.target_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D')) "; } sql += criterionOnIntermediateCRS; } auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode}; std::string additionalWhere( "AND v1.deprecated = 0 AND v2.deprecated = 0 " "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, " "south_lat2, west_lon2, north_lat2, east_lon2) = 1 "); if (!allowedAuthorities.empty()) { additionalWhere += "AND v1.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ") AND v2.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ')'; for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } } if (d->hasAuthorityRestriction()) { additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? "; params.emplace_back(d->authority()); params.emplace_back(d->authority()); } for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { const double south_lat = bbox->southBoundLatitude(); const double west_lon = bbox->westBoundLongitude(); const double north_lat = bbox->northBoundLatitude(); const double east_lon = bbox->eastBoundLongitude(); if (south_lat != -90.0 || west_lon != -180.0 || north_lat != 90.0 || east_lon != 180.0) { additionalWhere += "AND intersects_bbox(south_lat1, " "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND " "intersects_bbox(south_lat2, west_lon2, " "north_lat2, east_lon2, ?, ?, ?, ?) "; params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); } } } } } const auto buildIntermediateWhere = [&intermediateCRSAuthCodes]( const std::string &first_field, const std::string &second_field) { if (intermediateCRSAuthCodes.empty()) { return std::string(); } std::string l_sql(" AND ("); for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) { if (i > 0) { l_sql += " OR"; } l_sql += "(v1." + first_field + "_crs_auth_name = ? AND "; l_sql += "v1." + first_field + "_crs_code = ? AND "; l_sql += "v2." + second_field + "_crs_auth_name = ? AND "; l_sql += "v2." + second_field + "_crs_code = ?) "; } l_sql += ')'; return l_sql; }; std::string intermediateWhere = buildIntermediateWhere("target", "source"); for (const auto &pair : intermediateCRSAuthCodes) { params.emplace_back(pair.first); params.emplace_back(pair.second); params.emplace_back(pair.first); params.emplace_back(pair.second); } auto res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); const auto filterOutSuperseded = [](SQLResultSet &&resultSet) { std::set> setTransf1; std::set> setTransf2; for (const auto &row : resultSet) { // table1 const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // accuracy1 // table2 const auto &auth_name2 = row[5]; const auto &code2 = row[6]; setTransf1.insert( std::pair(auth_name1, code1)); setTransf2.insert( std::pair(auth_name2, code2)); } SQLResultSet filteredResultSet; for (const auto &row : resultSet) { const auto &replacement_auth_name1 = row[16]; const auto &replacement_code1 = row[17]; const auto &replacement_auth_name2 = row[18]; const auto &replacement_code2 = row[19]; if (!replacement_auth_name1.empty() && setTransf1.find(std::pair( replacement_auth_name1, replacement_code1)) != setTransf1.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } if (!replacement_auth_name2.empty() && setTransf2.find(std::pair( replacement_auth_name2, replacement_code2)) != setTransf2.end()) { // Skip transformations that are superseded by others that got // returned in the result set. continue; } filteredResultSet.emplace_back(row); } return filteredResultSet; }; if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata({op1, op2}, false)); } // Case (source->intermediate) and (target->intermediate) sql = sqlProlog + "ON v1.target_crs_auth_name = v2.target_crs_auth_name " "AND v1.target_crs_code = v2.target_crs_code " + joinArea + "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? " "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("target", "target"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {op1, op2->inverse()}, false)); } // Case (intermediate->source) and (intermediate->target) sql = sqlProlog + "ON v1.source_crs_auth_name = v2.source_crs_auth_name " "AND v1.source_crs_code = v2.source_crs_code " + joinArea + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { if (!minDate.empty()) { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x " "JOIN geodetic_datum y " "ON " "y.auth_name = x.datum_auth_name AND " "y.code = x.datum_code " "WHERE " "x.auth_name = v1.source_crs_auth_name AND " "x.code = v1.source_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D') AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } else { criterionOnIntermediateCRS = "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE " "x.auth_name = v1.source_crs_auth_name AND " "x.code = v1.source_crs_code AND " "x.type IN ('geographic 2D', 'geographic 3D')) "; } sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("source", "source"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {op1->inverse(), op2}, false)); } // Case (intermediate->source) and (target->intermediate) sql = sqlProlog + "ON v1.source_crs_auth_name = v2.target_crs_auth_name " "AND v1.source_crs_code = v2.target_crs_code " + joinArea + "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? " "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? "; if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) { sql += criterionOnIntermediateCRS; } intermediateWhere = buildIntermediateWhere("source", "target"); res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; // const auto &accuracy1 = row[3]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; // const auto &accuracy2 = row[7]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata( {op1->inverse(), op2->inverse()}, false)); } std::vector list; for (const auto &op : listTmp) { if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } return list; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::vector AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates( const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName, const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS, const std::string &targetCRSAuthName, const std::string &targetCRSCode, bool usePROJAlternativeGridNames, bool discardIfMissingGrid, bool considerKnownGridsAsAvailable, bool discardSuperseded, const std::vector &allowedAuthorities, const metadata::ExtentPtr &intersectingExtent1, const metadata::ExtentPtr &intersectingExtent2) const { std::vector listTmp; if (sourceCRSAuthName == targetCRSAuthName && sourceCRSCode == targetCRSCode) { return listTmp; } std::string minDate; const auto &sourceDatum = dynamic_cast(sourceCRS.get())->datum(); const auto &targetDatum = dynamic_cast(targetCRS.get())->datum(); if (sourceDatum && sourceDatum->publicationDate().has_value() && targetDatum && targetDatum->publicationDate().has_value()) { const auto sourceDate(sourceDatum->publicationDate()->toString()); const auto targetDate(targetDatum->publicationDate()->toString()); minDate = std::min(sourceDate, targetDate); } // For some reason, filtering on v1.deprecated and v2.deprecated kills // performance const std::string sqlProlog("SELECT v1.table_name as table1, " "v1.auth_name AS auth_name1, v1.code AS code1, " "v1.deprecated AS deprecated1, " "v2.table_name as table2, " "v2.auth_name AS auth_name2, v2.code AS code2, " "v2.deprecated AS deprecated2 " "FROM coordinate_operation_view v1 " "JOIN coordinate_operation_view v2 " "JOIN geodetic_crs g_source " "JOIN geodetic_crs g_v1s " "JOIN geodetic_crs g_v1t " "JOIN geodetic_crs g_v2s " "JOIN geodetic_crs g_v2t " "JOIN geodetic_crs g_target " "ON g_v1s.auth_name = v1.source_crs_auth_name " "AND g_v1s.code = v1.source_crs_code " "AND g_v1t.auth_name = v1.target_crs_auth_name " "AND g_v1t.code = v1.target_crs_code " "AND g_v2s.auth_name = v2.source_crs_auth_name " "AND g_v2s.code = v2.source_crs_code " "AND g_v2t.auth_name = v2.target_crs_auth_name " "AND g_v2t.code = v2.target_crs_code "); const std::string joinArea("JOIN usage u1 ON " "u1.object_table_name = v1.table_name AND " "u1.object_auth_name = v1.auth_name AND " "u1.object_code = v1.code " "JOIN extent a1 " "ON a1.auth_name = u1.extent_auth_name AND " "a1.code = u1.extent_code " "JOIN usage u2 ON " "u2.object_table_name = v2.table_name AND " "u2.object_auth_name = v2.auth_name AND " "u2.object_code = v2.code " "JOIN extent a2 " "ON a2.auth_name = u2.extent_auth_name AND " "a2.code = u2.extent_code "); auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode}; std::string additionalWhere(joinArea); additionalWhere += "WHERE g_source.auth_name = ? AND g_source.code = ? " "AND g_target.auth_name = ? AND g_target.code = ? " "AND intersects_bbox(" "a1.south_lat, a1.west_lon, a1.north_lat, a1.east_lon, " "a2.south_lat, a2.west_lon, a2.north_lat, a2.east_lon) = 1 "; #if 0 // While those additional constraints are correct, they are found to // kill performance. So enforce them as post-processing if (!allowedAuthorities.empty()) { additionalWhere += "AND v1.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ") AND v2.auth_name IN ("; for (size_t i = 0; i < allowedAuthorities.size(); i++) { if (i > 0) additionalWhere += ','; additionalWhere += '?'; } additionalWhere += ") "; for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } for (const auto &allowedAuthority : allowedAuthorities) { params.emplace_back(allowedAuthority); } } if (d->hasAuthorityRestriction()) { additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? "; params.emplace_back(d->authority()); params.emplace_back(d->authority()); } #endif for (const auto &extent : {intersectingExtent1, intersectingExtent2}) { if (extent) { const auto &geogExtent = extent->geographicElements(); if (geogExtent.size() == 1) { auto bbox = dynamic_cast( geogExtent[0].get()); if (bbox) { const double south_lat = bbox->southBoundLatitude(); const double west_lon = bbox->westBoundLongitude(); const double north_lat = bbox->northBoundLatitude(); const double east_lon = bbox->eastBoundLongitude(); if (south_lat != -90.0 || west_lon != -180.0 || north_lat != 90.0 || east_lon != 180.0) { additionalWhere += "AND intersects_bbox(a1.south_lat, a1.west_lon, " "a1.north_lat, a1.east_lon, ?, ?, ?, ?) AND " "intersects_bbox(a2.south_lat, a2.west_lon, " "a2.north_lat, a2.east_lon, ?, ?, ?, ?) "; params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); params.emplace_back(south_lat); params.emplace_back(west_lon); params.emplace_back(north_lat); params.emplace_back(east_lon); } } } } } // Case (source->intermediate) and (intermediate->target) std::string sql(sqlProlog + "AND g_v1t.datum_auth_name = g_v2s.datum_auth_name " "AND g_v1t.datum_code = g_v2s.datum_code " "AND g_v1s.datum_auth_name = g_source.datum_auth_name " "AND g_v1s.datum_code = g_source.datum_code " "AND g_v2t.datum_auth_name = g_target.datum_auth_name " "AND g_v2t.datum_code = g_target.datum_code "); if (!minDate.empty()) { sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y " "WHERE " "y.auth_name = g_v1t.datum_auth_name AND " "y.code = g_v1t.datum_code AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str()); auto res = d->run(sql + additionalWhere, params); // fprintf(stderr, "after\n"); const auto filterDeprecatedAndNotMatchingAuth = [&](SQLResultSet &&resultSet) { SQLResultSet filteredResultSet; for (const auto &row : resultSet) { const auto &deprecated1 = row[3]; const auto &deprecated2 = row[7]; if (deprecated1 == "1" || deprecated2 == "1") { continue; } const auto &auth_name1 = row[1]; const auto &auth_name2 = row[5]; if (d->hasAuthorityRestriction()) { if (auth_name1 != d->authority() || auth_name2 != d->authority()) { continue; } } if (!allowedAuthorities.empty()) { { bool found = false; for (const auto &auth : allowedAuthorities) { if (auth_name1 == auth) { found = true; break; } } if (!found) { continue; } } { bool found = false; for (const auto &auth : allowedAuthorities) { if (auth_name2 == auth) { found = true; break; } } if (!found) { continue; } } } filteredResultSet.emplace_back(row); } return filteredResultSet; }; const auto filterOutSuperseded = [&](SQLResultSet &&resultSet) { std::set> setTransf; std::string findSupersededSql( "SELECT superseded_table_name, " "superseded_auth_name, superseded_code, " "replacement_auth_name, replacement_code " "FROM supersession WHERE same_source_target_crs = 1 AND ("); bool findSupersededFirstWhere = true; ListOfParams findSupersededParams; std::set setAlreadyAsked; const auto keyMapSupersession = []( const std::string &table_name, const std::string &auth_name, const std::string &code) { return table_name + auth_name + code; }; for (const auto &row : resultSet) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto key1 = keyMapSupersession(table1, auth_name1, code1); if (setAlreadyAsked.find(key1) == setAlreadyAsked.end()) { setAlreadyAsked.insert(key1); if (!findSupersededFirstWhere) findSupersededSql += " OR "; findSupersededFirstWhere = false; findSupersededSql += "(superseded_table_name = ? AND replacement_table_name = " "superseded_table_name AND superseded_auth_name = ? AND " "superseded_code = ?)"; findSupersededParams.push_back(table1); findSupersededParams.push_back(auth_name1); findSupersededParams.push_back(code1); } const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; const auto key2 = keyMapSupersession(table2, auth_name2, code2); if (setAlreadyAsked.find(key2) == setAlreadyAsked.end()) { setAlreadyAsked.insert(key2); if (!findSupersededFirstWhere) findSupersededSql += " OR "; findSupersededFirstWhere = false; findSupersededSql += "(superseded_table_name = ? AND replacement_table_name = " "superseded_table_name AND superseded_auth_name = ? AND " "superseded_code = ?)"; findSupersededParams.push_back(table2); findSupersededParams.push_back(auth_name2); findSupersededParams.push_back(code2); } setTransf.insert( std::pair(auth_name1, code1)); setTransf.insert( std::pair(auth_name2, code2)); } findSupersededSql += ')'; std::map>> mapSupersession; if (!findSupersededParams.empty()) { const auto resSuperseded = d->run(findSupersededSql, findSupersededParams); for (const auto &row : resSuperseded) { const auto &superseded_table_name = row[0]; const auto &superseded_auth_name = row[1]; const auto &superseded_code = row[2]; const auto &replacement_auth_name = row[3]; const auto &replacement_code = row[4]; mapSupersession[keyMapSupersession(superseded_table_name, superseded_auth_name, superseded_code)] .push_back(std::pair( replacement_auth_name, replacement_code)); } } SQLResultSet filteredResultSet; for (const auto &row : resultSet) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; auto iter1 = mapSupersession.find( keyMapSupersession(table1, auth_name1, code1)); if (iter1 != mapSupersession.end()) { bool foundReplacement = false; for (const auto &replacement : iter1->second) { const auto &replacement_auth_name = replacement.first; const auto &replacement_code = replacement.second; if (setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others // that got // returned in the result set. foundReplacement = true; break; } } if (foundReplacement) { continue; } } auto iter2 = mapSupersession.find( keyMapSupersession(table2, auth_name2, code2)); if (iter2 != mapSupersession.end()) { bool foundReplacement = false; for (const auto &replacement : iter2->second) { const auto &replacement_auth_name = replacement.first; const auto &replacement_code = replacement.second; if (setTransf.find(std::pair( replacement_auth_name, replacement_code)) != setTransf.end()) { // Skip transformations that are superseded by others // that got // returned in the result set. foundReplacement = true; break; } } if (foundReplacement) { continue; } } filteredResultSet.emplace_back(row); } return filteredResultSet; }; res = filterDeprecatedAndNotMatchingAuth(std::move(res)); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } auto opFactory = operation::CoordinateOperationFactory::create(); for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } const auto &op1Source = op1->sourceCRS(); const auto &op1Target = op1->targetCRS(); const auto &op2Source = op2->sourceCRS(); const auto &op2Target = op2->targetCRS(); if (op1Source && op1Target && op2Source && op2Target) { std::vector steps; if (!sourceCRS->isEquivalentTo( op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opFirst = opFactory->createOperation( sourceCRS, NN_NO_CHECK(op1Source)); assert(opFirst); steps.emplace_back(NN_NO_CHECK(opFirst)); } steps.emplace_back(op1); if (!op1Target->isEquivalentTo( op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opMiddle = opFactory->createOperation( NN_NO_CHECK(op1Target), NN_NO_CHECK(op2Source)); assert(opMiddle); steps.emplace_back(NN_NO_CHECK(opMiddle)); } steps.emplace_back(op2); if (!op2Target->isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS); assert(opLast); steps.emplace_back(NN_NO_CHECK(opLast)); } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata(steps, false)); } } // Case (source->intermediate) and (target->intermediate) sql = sqlProlog + "AND g_v1t.datum_auth_name = g_v2t.datum_auth_name " "AND g_v1t.datum_code = g_v2t.datum_code " "AND g_v1s.datum_auth_name = g_source.datum_auth_name " "AND g_v1s.datum_code = g_source.datum_code " "AND g_v2s.datum_auth_name = g_target.datum_auth_name " "AND g_v2s.datum_code = g_target.datum_code "; if (!minDate.empty()) { sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y " "WHERE " "y.auth_name = g_v1t.datum_auth_name AND " "y.code = g_v1t.datum_code AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str()); res = d->run(sql + additionalWhere, params); // fprintf(stderr, "after\n"); res = filterDeprecatedAndNotMatchingAuth(std::move(res)); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } const auto &op1Source = op1->sourceCRS(); const auto &op1Target = op1->targetCRS(); const auto &op2Source = op2->sourceCRS(); const auto &op2Target = op2->targetCRS(); if (op1Source && op1Target && op2Source && op2Target) { std::vector steps; if (!sourceCRS->isEquivalentTo( op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opFirst = opFactory->createOperation( sourceCRS, NN_NO_CHECK(op1Source)); assert(opFirst); steps.emplace_back(NN_NO_CHECK(opFirst)); } steps.emplace_back(op1); if (!op1Target->isEquivalentTo( op2Target.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opMiddle = opFactory->createOperation( NN_NO_CHECK(op1Target), NN_NO_CHECK(op2Target)); assert(opMiddle); steps.emplace_back(NN_NO_CHECK(opMiddle)); } steps.emplace_back(op2->inverse()); if (!op2Source->isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Source), targetCRS); assert(opLast); steps.emplace_back(NN_NO_CHECK(opLast)); } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata(steps, false)); } } // Case (intermediate->source) and (intermediate->target) sql = sqlProlog + "AND g_v1s.datum_auth_name = g_v2s.datum_auth_name " "AND g_v1s.datum_code = g_v2s.datum_code " "AND g_v1t.datum_auth_name = g_source.datum_auth_name " "AND g_v1t.datum_code = g_source.datum_code " "AND g_v2t.datum_auth_name = g_target.datum_auth_name " "AND g_v2t.datum_code = g_target.datum_code "; if (!minDate.empty()) { sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y " "WHERE " "y.auth_name = g_v1s.datum_auth_name AND " "y.code = g_v1s.datum_code AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str()); res = d->run(sql + additionalWhere, params); // fprintf(stderr, "after\n"); res = filterDeprecatedAndNotMatchingAuth(std::move(res)); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } const auto &op1Source = op1->sourceCRS(); const auto &op1Target = op1->targetCRS(); const auto &op2Source = op2->sourceCRS(); const auto &op2Target = op2->targetCRS(); if (op1Source && op1Target && op2Source && op2Target) { std::vector steps; if (!sourceCRS->isEquivalentTo( op1Target.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opFirst = opFactory->createOperation( sourceCRS, NN_NO_CHECK(op1Target)); assert(opFirst); steps.emplace_back(NN_NO_CHECK(opFirst)); } steps.emplace_back(op1->inverse()); if (!op1Source->isEquivalentTo( op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opMiddle = opFactory->createOperation( NN_NO_CHECK(op1Source), NN_NO_CHECK(op2Source)); assert(opMiddle); steps.emplace_back(NN_NO_CHECK(opMiddle)); } steps.emplace_back(op2); if (!op2Target->isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS); assert(opLast); steps.emplace_back(NN_NO_CHECK(opLast)); } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata(steps, false)); } } // Case (intermediate->source) and (target->intermediate) sql = sqlProlog + "AND g_v1s.datum_auth_name = g_v2t.datum_auth_name " "AND g_v1s.datum_code = g_v2t.datum_code " "AND g_v1t.datum_auth_name = g_source.datum_auth_name " "AND g_v1t.datum_code = g_source.datum_code " "AND g_v2s.datum_auth_name = g_target.datum_auth_name " "AND g_v2s.datum_code = g_target.datum_code "; if (!minDate.empty()) { sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y " "WHERE " "y.auth_name = g_v1s.datum_auth_name AND " "y.code = g_v1s.datum_code AND " "(y.publication_date IS NULL OR " "(y.publication_date >= '" + minDate + "'))) "; } // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str()); res = d->run(sql + additionalWhere, params); // fprintf(stderr, "after\n"); res = filterDeprecatedAndNotMatchingAuth(std::move(res)); if (discardSuperseded) { res = filterOutSuperseded(std::move(res)); } for (const auto &row : res) { const auto &table1 = row[0]; const auto &auth_name1 = row[1]; const auto &code1 = row[2]; const auto &table2 = row[4]; const auto &auth_name2 = row[5]; const auto &code2 = row[6]; auto op1 = d->createFactory(auth_name1) ->createCoordinateOperation( code1, true, usePROJAlternativeGridNames, table1); if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } auto op2 = d->createFactory(auth_name2) ->createCoordinateOperation( code2, true, usePROJAlternativeGridNames, table2); if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode, targetCRSAuthName, targetCRSCode)) { continue; } const auto &op1Source = op1->sourceCRS(); const auto &op1Target = op1->targetCRS(); const auto &op2Source = op2->sourceCRS(); const auto &op2Target = op2->targetCRS(); if (op1Source && op1Target && op2Source && op2Target) { std::vector steps; if (!sourceCRS->isEquivalentTo( op1Target.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opFirst = opFactory->createOperation( sourceCRS, NN_NO_CHECK(op1Target)); assert(opFirst); steps.emplace_back(NN_NO_CHECK(opFirst)); } steps.emplace_back(op1->inverse()); if (!op1Source->isEquivalentTo( op2Target.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opMiddle = opFactory->createOperation( NN_NO_CHECK(op1Source), NN_NO_CHECK(op2Target)); assert(opMiddle); steps.emplace_back(NN_NO_CHECK(opMiddle)); } steps.emplace_back(op2->inverse()); if (!op2Source->isEquivalentTo( targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) { auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Source), targetCRS); assert(opLast); steps.emplace_back(NN_NO_CHECK(opLast)); } listTmp.emplace_back( operation::ConcatenatedOperation::createComputeMetadata(steps, false)); } } std::vector list; for (const auto &op : listTmp) { if (!discardIfMissingGrid || !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) { list.emplace_back(op); } } return list; } //! @endcond // --------------------------------------------------------------------------- /** \brief Returns the authority name associated to this factory. * @return name. */ const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN { return d->authority(); } // --------------------------------------------------------------------------- /** \brief Returns the set of authority codes of the given object type. * * @param type Object type. * @param allowDeprecated whether we should return deprecated objects as well. * @return the set of authority codes for spatial reference objects of the given * type * @throw FactoryException */ std::set AuthorityFactory::getAuthorityCodes(const ObjectType &type, bool allowDeprecated) const { std::string sql; switch (type) { case ObjectType::PRIME_MERIDIAN: sql = "SELECT code FROM prime_meridian WHERE "; break; case ObjectType::ELLIPSOID: sql = "SELECT code FROM ellipsoid WHERE "; break; case ObjectType::DATUM: sql = "SELECT code FROM object_view WHERE table_name IN " "('geodetic_datum', 'vertical_datum') AND "; break; case ObjectType::GEODETIC_REFERENCE_FRAME: sql = "SELECT code FROM geodetic_datum WHERE "; break; case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: sql = "SELECT code FROM geodetic_datum WHERE " "frame_reference_epoch IS NOT NULL AND "; break; case ObjectType::VERTICAL_REFERENCE_FRAME: sql = "SELECT code FROM vertical_datum WHERE "; break; case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: sql = "SELECT code FROM vertical_datum WHERE " "frame_reference_epoch IS NOT NULL AND "; break; case ObjectType::CRS: sql = "SELECT code FROM crs_view WHERE "; break; case ObjectType::GEODETIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE "; break; case ObjectType::GEOCENTRIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE type " "= " GEOCENTRIC_SINGLE_QUOTED " AND "; break; case ObjectType::GEOGRAPHIC_CRS: sql = "SELECT code FROM geodetic_crs WHERE type IN " "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND "; break; case ObjectType::GEOGRAPHIC_2D_CRS: sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED " AND "; break; case ObjectType::GEOGRAPHIC_3D_CRS: sql = "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED " AND "; break; case ObjectType::VERTICAL_CRS: sql = "SELECT code FROM vertical_crs WHERE "; break; case ObjectType::PROJECTED_CRS: sql = "SELECT code FROM projected_crs WHERE "; break; case ObjectType::COMPOUND_CRS: sql = "SELECT code FROM compound_crs WHERE "; break; case ObjectType::COORDINATE_OPERATION: sql = "SELECT code FROM coordinate_operation_with_conversion_view WHERE "; break; case ObjectType::CONVERSION: sql = "SELECT code FROM conversion WHERE "; break; case ObjectType::TRANSFORMATION: sql = "SELECT code FROM coordinate_operation_view WHERE table_name != " "'concatenated_operation' AND "; break; case ObjectType::CONCATENATED_OPERATION: sql = "SELECT code FROM concatenated_operation WHERE "; break; case ObjectType::DATUM_ENSEMBLE: sql = "SELECT code FROM object_view WHERE table_name IN " "('geodetic_datum', 'vertical_datum') AND " "type = 'ensemble' AND "; break; } sql += "auth_name = ?"; if (!allowDeprecated) { sql += " AND deprecated = 0"; } auto res = d->run(sql, {d->authority()}); std::set set; for (const auto &row : res) { set.insert(row[0]); } return set; } // --------------------------------------------------------------------------- /** \brief Gets a description of the object corresponding to a code. * * \note In case of several objects of different types with the same code, * one of them will be arbitrarily selected. But if a CRS object is found, it * will be selected. * * @param code Object code allocated by authority. (e.g. "4326") * @return description. * @throw NoSuchAuthorityCodeException * @throw FactoryException */ std::string AuthorityFactory::getDescriptionText(const std::string &code) const { auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? " "AND code = ? ORDER BY table_name"; auto sqlRes = d->runWithCodeParam(sql, code); if (sqlRes.empty()) { throw NoSuchAuthorityCodeException("object not found", d->authority(), code); } std::string text; for (const auto &row : sqlRes) { const auto &tableName = row[1]; if (tableName == "geodetic_crs" || tableName == "projected_crs" || tableName == "vertical_crs" || tableName == "compound_crs") { return row[0]; } else if (text.empty()) { text = row[0]; } } return text; } // --------------------------------------------------------------------------- /** \brief Return a list of information on CRS objects * * This is functionnaly equivalent of listing the codes from an authority, * instantiating * a CRS object for each of them and getting the information from this CRS * object, but this implementation has much less overhead. * * @throw FactoryException */ std::list AuthorityFactory::getCRSInfoList() const { const auto getSqlArea = [](const std::string &table_name) { return "JOIN usage u ON " "u.object_table_name = '" + table_name + "' AND " "u.object_auth_name = c.auth_name AND " "u.object_code = c.code " "JOIN extent a " "ON a.auth_name = u.extent_auth_name AND " "a.code = u.extent_code "; }; std::string sql = "SELECT c.auth_name, c.code, c.name, c.type, " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL FROM geodetic_crs c " + getSqlArea("geodetic_crs"); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); } sql += " UNION ALL "; sql += "SELECT c.auth_name, c.code, c.name, 'projected', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, cm.name AS conversion_method_name FROM " "projected_crs c " + getSqlArea("projected_crs") + "LEFT JOIN conversion_table conv ON " "c.conversion_auth_name = conv.auth_name AND " "c.conversion_code = conv.code " "LEFT JOIN conversion_method cm ON " "conv.method_auth_name = cm.auth_name AND " "conv.method_code = cm.code"; if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); } sql += " UNION ALL "; sql += "SELECT c.auth_name, c.code, c.name, 'vertical', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL FROM vertical_crs c " + getSqlArea("vertical_crs"); if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); } sql += " UNION ALL "; sql += "SELECT c.auth_name, c.code, c.name, 'compound', " "c.deprecated, " "a.west_lon, a.south_lat, a.east_lon, a.north_lat, " "a.description, NULL FROM compound_crs c " + getSqlArea("compound_crs"); if (d->hasAuthorityRestriction()) { sql += " WHERE c.auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { AuthorityFactory::CRSInfo info; info.authName = row[0]; info.code = row[1]; info.name = row[2]; const auto &type = row[3]; if (type == GEOG_2D) { info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS; } else if (type == GEOG_3D) { info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS; } else if (type == GEOCENTRIC) { info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS; } else if (type == PROJECTED) { info.type = AuthorityFactory::ObjectType::PROJECTED_CRS; } else if (type == VERTICAL) { info.type = AuthorityFactory::ObjectType::VERTICAL_CRS; } else if (type == COMPOUND) { info.type = AuthorityFactory::ObjectType::COMPOUND_CRS; } info.deprecated = row[4] == "1"; if (row[5].empty()) { info.bbox_valid = false; } else { info.bbox_valid = true; info.west_lon_degree = c_locale_stod(row[5]); info.south_lat_degree = c_locale_stod(row[6]); info.east_lon_degree = c_locale_stod(row[7]); info.north_lat_degree = c_locale_stod(row[8]); } info.areaName = row[9]; info.projectionMethodName = row[10]; res.emplace_back(info); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress AuthorityFactory::UnitInfo::UnitInfo() : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{}, deprecated{} {} //! @endcond // --------------------------------------------------------------------------- /** \brief Return the list of units. * @throw FactoryException * * @since 7.1 */ std::list AuthorityFactory::getUnitList() const { std::string sql = "SELECT auth_name, code, name, type, conv_factor, " "proj_short_name, deprecated FROM unit_of_measure"; ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " WHERE auth_name = ?"; params.emplace_back(d->authority()); } sql += " ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { AuthorityFactory::UnitInfo info; info.authName = row[0]; info.code = row[1]; info.name = row[2]; const std::string &raw_category(row[3]); if (raw_category == "length") { info.category = info.name.find(" per ") != std::string::npos ? "linear_per_time" : "linear"; } else if (raw_category == "angle") { info.category = info.name.find(" per ") != std::string::npos ? "angular_per_time" : "angular"; } else if (raw_category == "scale") { info.category = info.name.find(" per year") != std::string::npos || info.name.find(" per second") != std::string::npos ? "scale_per_time" : "scale"; } else { info.category = raw_category; } info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]); info.projShortName = row[5]; info.deprecated = row[6] == "1"; res.emplace_back(info); } return res; } // --------------------------------------------------------------------------- /** \brief Gets the official name from a possibly alias name. * * @param aliasedName Alias name. * @param tableName Table name/category. Can help in case of ambiguities. * Or empty otherwise. * @param source Source of the alias. Can help in case of ambiguities. * Or empty otherwise. * @param tryEquivalentNameSpelling whether the comparison of aliasedName with * the alt_name column of the alis_name table should be done with using * metadata::Identifier::isEquivalentName() rather than strict string * comparison; * @param outTableName Table name in which the official name has been found. * @param outAuthName Authority name of the official name that has been found. * @param outCode Code of the official name that has been found. * @return official name (or empty if not found). * @throw FactoryException */ std::string AuthorityFactory::getOfficialNameFromAlias( const std::string &aliasedName, const std::string &tableName, const std::string &source, bool tryEquivalentNameSpelling, std::string &outTableName, std::string &outAuthName, std::string &outCode) const { if (tryEquivalentNameSpelling) { std::string sql( "SELECT table_name, auth_name, code, alt_name FROM alias_name"); ListOfParams params; if (!tableName.empty()) { sql += " WHERE table_name = ?"; params.push_back(tableName); } if (!source.empty()) { if (!tableName.empty()) { sql += " AND "; } else { sql += " WHERE "; } sql += "source = ?"; params.push_back(source); } auto res = d->run(sql, params); if (res.empty()) { return std::string(); } for (const auto &row : res) { const auto &alt_name = row[3]; if (metadata::Identifier::isEquivalentName(alt_name.c_str(), aliasedName.c_str())) { outTableName = row[0]; outAuthName = row[1]; outCode = row[2]; sql = "SELECT name FROM \""; sql += replaceAll(outTableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; res = d->run(sql, {outAuthName, outCode}); if (res.empty()) { // shouldn't happen normally return std::string(); } return res.front()[0]; } } return std::string(); } else { std::string sql( "SELECT table_name, auth_name, code FROM alias_name WHERE " "alt_name = ?"); ListOfParams params{aliasedName}; if (!tableName.empty()) { sql += " AND table_name = ?"; params.push_back(tableName); } if (!source.empty()) { sql += " AND source = ?"; params.push_back(source); } auto res = d->run(sql, params); if (res.empty()) { return std::string(); } params.clear(); sql.clear(); bool first = true; for (const auto &row : res) { if (!first) sql += " UNION ALL "; first = false; outTableName = row[0]; outAuthName = row[1]; outCode = row[2]; sql += "SELECT name, ? AS table_name, auth_name, code, deprecated " "FROM \""; sql += replaceAll(outTableName, "\"", "\"\""); sql += "\" WHERE auth_name = ? AND code = ?"; params.emplace_back(outTableName); params.emplace_back(outAuthName); params.emplace_back(outCode); } sql = "SELECT name, table_name, auth_name, code FROM (" + sql + ") x ORDER BY deprecated LIMIT 1"; res = d->run(sql, params); if (res.empty()) { // shouldn't happen normally return std::string(); } const auto &row = res.front(); outTableName = row[1]; outAuthName = row[2]; outCode = row[3]; return row[0]; } } // --------------------------------------------------------------------------- /** \brief Return a list of objects, identified by their name * * @param searchedName Searched name. Must be at least 2 character long. * @param allowedObjectTypes List of object types into which to search. If * empty, all object types will be searched. * @param approximateMatch Whether approximate name identification is allowed. * @param limitResultCount Maximum number of results to return. * Or 0 for unlimited. * @return list of matched objects. * @throw FactoryException */ std::list AuthorityFactory::createObjectsFromName( const std::string &searchedName, const std::vector &allowedObjectTypes, bool approximateMatch, size_t limitResultCount) const { std::list res; const auto resTmp(createObjectsFromNameEx( searchedName, allowedObjectTypes, approximateMatch, limitResultCount)); for (const auto &pair : resTmp) { res.emplace_back(pair.first); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress /** \brief Return a list of objects, identifier by their name, with the name * on which the match occurred. * * The name on which the match occurred might be different from the object name, * if the match has been done on an alias name of that object. * * @param searchedName Searched name. Must be at least 2 character long. * @param allowedObjectTypes List of object types into which to search. If * empty, all object types will be searched. * @param approximateMatch Whether approximate name identification is allowed. * @param limitResultCount Maximum number of results to return. * Or 0 for unlimited. * @return list of matched objects. * @throw FactoryException */ std::list AuthorityFactory::createObjectsFromNameEx( const std::string &searchedName, const std::vector &allowedObjectTypes, bool approximateMatch, size_t limitResultCount) const { std::string searchedNameWithoutDeprecated(searchedName); bool deprecated = false; if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) { deprecated = true; searchedNameWithoutDeprecated.resize( searchedNameWithoutDeprecated.size() - strlen(" (deprecated)")); } const std::string canonicalizedSearchedName( metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated)); if (canonicalizedSearchedName.size() <= 1) { return {}; } std::string sql( "SELECT table_name, auth_name, code, name, deprecated, is_alias " "FROM ("); const auto getTableAndTypeConstraints = [&allowedObjectTypes, &searchedName]() { typedef std::pair TableType; std::list res; // Hide ESRI D_ vertical datums const bool startsWithDUnderscore = starts_with(searchedName, "D_"); if (allowedObjectTypes.empty()) { for (const auto &tableName : {"prime_meridian", "ellipsoid", "geodetic_datum", "vertical_datum", "geodetic_crs", "projected_crs", "vertical_crs", "compound_crs", "conversion", "helmert_transformation", "grid_transformation", "other_transformation", "concatenated_operation"}) { if (!(startsWithDUnderscore && strcmp(tableName, "vertical_datum") == 0)) { res.emplace_back(TableType(tableName, std::string())); } } } else { for (const auto type : allowedObjectTypes) { switch (type) { case ObjectType::PRIME_MERIDIAN: res.emplace_back( TableType("prime_meridian", std::string())); break; case ObjectType::ELLIPSOID: res.emplace_back(TableType("ellipsoid", std::string())); break; case ObjectType::DATUM: res.emplace_back( TableType("geodetic_datum", std::string())); if (!startsWithDUnderscore) { res.emplace_back( TableType("vertical_datum", std::string())); } break; case ObjectType::GEODETIC_REFERENCE_FRAME: res.emplace_back( TableType("geodetic_datum", std::string())); break; case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME: res.emplace_back( TableType("geodetic_datum", "frame_reference_epoch")); break; case ObjectType::VERTICAL_REFERENCE_FRAME: res.emplace_back( TableType("vertical_datum", std::string())); break; case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME: res.emplace_back( TableType("vertical_datum", "frame_reference_epoch")); break; case ObjectType::CRS: res.emplace_back(TableType("geodetic_crs", std::string())); res.emplace_back(TableType("projected_crs", std::string())); res.emplace_back(TableType("vertical_crs", std::string())); res.emplace_back(TableType("compound_crs", std::string())); break; case ObjectType::GEODETIC_CRS: res.emplace_back(TableType("geodetic_crs", std::string())); break; case ObjectType::GEOCENTRIC_CRS: res.emplace_back(TableType("geodetic_crs", GEOCENTRIC)); break; case ObjectType::GEOGRAPHIC_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_2D)); res.emplace_back(TableType("geodetic_crs", GEOG_3D)); break; case ObjectType::GEOGRAPHIC_2D_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_2D)); break; case ObjectType::GEOGRAPHIC_3D_CRS: res.emplace_back(TableType("geodetic_crs", GEOG_3D)); break; case ObjectType::PROJECTED_CRS: res.emplace_back(TableType("projected_crs", std::string())); break; case ObjectType::VERTICAL_CRS: res.emplace_back(TableType("vertical_crs", std::string())); break; case ObjectType::COMPOUND_CRS: res.emplace_back(TableType("compound_crs", std::string())); break; case ObjectType::COORDINATE_OPERATION: res.emplace_back(TableType("conversion", std::string())); res.emplace_back( TableType("helmert_transformation", std::string())); res.emplace_back( TableType("grid_transformation", std::string())); res.emplace_back( TableType("other_transformation", std::string())); res.emplace_back( TableType("concatenated_operation", std::string())); break; case ObjectType::CONVERSION: res.emplace_back(TableType("conversion", std::string())); break; case ObjectType::TRANSFORMATION: res.emplace_back( TableType("helmert_transformation", std::string())); res.emplace_back( TableType("grid_transformation", std::string())); res.emplace_back( TableType("other_transformation", std::string())); break; case ObjectType::CONCATENATED_OPERATION: res.emplace_back( TableType("concatenated_operation", std::string())); break; case ObjectType::DATUM_ENSEMBLE: res.emplace_back(TableType("geodetic_datum", "ensemble")); res.emplace_back(TableType("vertical_datum", "ensemble")); break; } } } return res; }; bool datumEnsembleAllowed = false; if (allowedObjectTypes.empty()) { datumEnsembleAllowed = true; } else { for (const auto type : allowedObjectTypes) { if (type == ObjectType::DATUM_ENSEMBLE) { datumEnsembleAllowed = true; break; } } } const auto listTableNameType = getTableAndTypeConstraints(); bool first = true; ListOfParams params; for (const auto &tableNameTypePair : listTableNameType) { if (!first) { sql += " UNION "; } first = false; sql += "SELECT '"; sql += tableNameTypePair.first; sql += "' AS table_name, auth_name, code, name, deprecated, " "0 AS is_alias FROM "; sql += tableNameTypePair.first; sql += " WHERE 1 = 1 "; if (!tableNameTypePair.second.empty()) { if (tableNameTypePair.second == "frame_reference_epoch") { sql += "AND frame_reference_epoch IS NOT NULL "; } else if (tableNameTypePair.second == "ensemble") { sql += "AND ensemble_accuracy IS NOT NULL "; } else { sql += "AND type = '"; sql += tableNameTypePair.second; sql += "' "; } } if (deprecated) { sql += "AND deprecated = 1 "; } if (!approximateMatch) { sql += "AND name = ? COLLATE NOCASE "; params.push_back(searchedNameWithoutDeprecated); } if (d->hasAuthorityRestriction()) { sql += "AND auth_name = ? "; params.emplace_back(d->authority()); } sql += " UNION SELECT '"; sql += tableNameTypePair.first; sql += "' AS table_name, " "ov.auth_name AS auth_name, " "ov.code AS code, a.alt_name AS name, " "ov.deprecated AS deprecated, 1 as is_alias FROM "; sql += tableNameTypePair.first; sql += " ov " "JOIN alias_name a ON " "ov.auth_name = a.auth_name AND ov.code = a.code WHERE " "a.table_name = '"; sql += tableNameTypePair.first; sql += "' "; if (!tableNameTypePair.second.empty()) { if (tableNameTypePair.second == "frame_reference_epoch") { sql += "AND ov.frame_reference_epoch IS NOT NULL "; } else if (tableNameTypePair.second == "ensemble") { sql += "AND ov.ensemble_accuracy IS NOT NULL "; } else { sql += "AND ov.type = '"; sql += tableNameTypePair.second; sql += "' "; } } if (deprecated) { sql += "AND ov.deprecated = 1 "; } if (!approximateMatch) { sql += "AND a.alt_name = ? COLLATE NOCASE "; params.push_back(searchedNameWithoutDeprecated); } if (d->hasAuthorityRestriction()) { sql += "AND ov.auth_name = ? "; params.emplace_back(d->authority()); } } sql += ") ORDER BY deprecated, is_alias, length(name), name"; if (limitResultCount > 0 && limitResultCount < static_cast(std::numeric_limits::max()) && !approximateMatch) { sql += " LIMIT "; sql += toString(static_cast(limitResultCount)); } std::list res; std::set> setIdentified; // Querying geodetic datum is a super hot path when importing from WKT1 // so cache results. if (allowedObjectTypes.size() == 1 && allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME && approximateMatch && d->authority().empty()) { auto &mapCanonicalizeGRFName = d->context()->getPrivate()->getMapCanonicalizeGRFName(); if (mapCanonicalizeGRFName.empty()) { auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &name = row[3]; const auto &deprecatedStr = row[4]; const auto canonicalizedName( metadata::Identifier::canonicalizeName(name)); auto &v = mapCanonicalizeGRFName[canonicalizedName]; if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") { v.push_back(row); } } } auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName); if (iter != mapCanonicalizeGRFName.end()) { const auto &listOfRow = iter->second; for (const auto &row : listOfRow) { const auto &auth_name = row[1]; const auto &code = row[2]; const auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(key); auto factory = d->createFactory(auth_name); const auto &name = row[3]; res.emplace_back( PairObjectName(factory->createGeodeticDatum(code), name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } } else { for (const auto &pair : mapCanonicalizeGRFName) { const auto &listOfRow = pair.second; for (const auto &row : listOfRow) { const auto &name = row[3]; bool match = ci_find(name, searchedNameWithoutDeprecated) != std::string::npos; if (!match) { const auto &canonicalizedName(pair.first); match = ci_find(canonicalizedName, canonicalizedSearchedName) != std::string::npos; } if (!match) { continue; } const auto &auth_name = row[1]; const auto &code = row[2]; const auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(key); auto factory = d->createFactory(auth_name); res.emplace_back(PairObjectName( factory->createGeodeticDatum(code), name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } } } else { auto sqlRes = d->run(sql, params); bool isFirst = true; bool firstIsDeprecated = false; for (const auto &row : sqlRes) { const auto &name = row[3]; if (approximateMatch) { bool match = ci_find(name, searchedNameWithoutDeprecated) != std::string::npos; if (!match) { const auto canonicalizedName( metadata::Identifier::canonicalizeName(name)); match = ci_find(canonicalizedName, canonicalizedSearchedName) != std::string::npos; } if (!match) { continue; } } const auto &table_name = row[0]; const auto &auth_name = row[1]; const auto &code = row[2]; const auto key = std::pair(auth_name, code); if (setIdentified.find(key) != setIdentified.end()) { continue; } setIdentified.insert(key); const auto &deprecatedStr = row[4]; if (isFirst) { firstIsDeprecated = deprecatedStr == "1"; isFirst = false; } if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) { break; } auto factory = d->createFactory(auth_name); auto getObject = [&factory, datumEnsembleAllowed]( const std::string &l_table_name, const std::string &l_code) -> common::IdentifiedObjectNNPtr { if (l_table_name == "prime_meridian") { return factory->createPrimeMeridian(l_code); } else if (l_table_name == "ellipsoid") { return factory->createEllipsoid(l_code); } else if (l_table_name == "geodetic_datum") { if (datumEnsembleAllowed) { datum::GeodeticReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; factory->createGeodeticDatumOrEnsemble( l_code, datum, datumEnsemble, turnEnsembleAsDatum); if (datum) { return NN_NO_CHECK(datum); } assert(datumEnsemble); return NN_NO_CHECK(datumEnsemble); } return factory->createGeodeticDatum(l_code); } else if (l_table_name == "vertical_datum") { if (datumEnsembleAllowed) { datum::VerticalReferenceFramePtr datum; datum::DatumEnsemblePtr datumEnsemble; constexpr bool turnEnsembleAsDatum = false; factory->createVerticalDatumOrEnsemble( l_code, datum, datumEnsemble, turnEnsembleAsDatum); if (datum) { return NN_NO_CHECK(datum); } assert(datumEnsemble); return NN_NO_CHECK(datumEnsemble); } return factory->createVerticalDatum(l_code); } else if (l_table_name == "geodetic_crs") { return factory->createGeodeticCRS(l_code); } else if (l_table_name == "projected_crs") { return factory->createProjectedCRS(l_code); } else if (l_table_name == "vertical_crs") { return factory->createVerticalCRS(l_code); } else if (l_table_name == "compound_crs") { return factory->createCompoundCRS(l_code); } else if (l_table_name == "conversion") { return factory->createConversion(l_code); } else if (l_table_name == "grid_transformation" || l_table_name == "helmert_transformation" || l_table_name == "other_transformation" || l_table_name == "concatenated_operation") { return factory->createCoordinateOperation(l_code, true); } throw std::runtime_error("Unsupported table_name"); }; res.emplace_back(PairObjectName(getObject(table_name, code), name)); if (limitResultCount > 0 && res.size() == limitResultCount) { break; } } } auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) { const auto &aName = a.first->nameStr(); const auto &bName = b.first->nameStr(); if (aName.size() < bName.size()) { return true; } if (aName.size() > bName.size()) { return false; } const auto &aIds = a.first->identifiers(); const auto &bIds = b.first->identifiers(); if (aIds.size() < bIds.size()) { return true; } if (aIds.size() > bIds.size()) { return false; } for (size_t idx = 0; idx < aIds.size(); idx++) { const auto &aCodeSpace = *aIds[idx]->codeSpace(); const auto &bCodeSpace = *bIds[idx]->codeSpace(); const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace); if (codeSpaceComparison < 0) { return true; } if (codeSpaceComparison > 0) { return false; } const auto &aCode = aIds[idx]->code(); const auto &bCode = bIds[idx]->code(); const auto codeComparison = aCode.compare(bCode); if (codeComparison < 0) { return true; } if (codeComparison > 0) { return false; } } return strcmp(typeid(a.first.get()).name(), typeid(b.first.get()).name()) < 0; }; res.sort(sortLambda); return res; } //! @endcond // --------------------------------------------------------------------------- /** \brief Return a list of area of use from their name * * @param name Searched name. * @param approximateMatch Whether approximate name identification is allowed. * @return list of (auth_name, code) of matched objects. * @throw FactoryException */ std::list> AuthorityFactory::listAreaOfUseFromName(const std::string &name, bool approximateMatch) const { std::string sql( "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND "); ListOfParams params; if (d->hasAuthorityRestriction()) { sql += " auth_name = ? AND "; params.emplace_back(d->authority()); } sql += "name LIKE ?"; if (!approximateMatch) { params.push_back(name); } else { params.push_back('%' + name + '%'); } auto sqlRes = d->run(sql, params); std::list> res; for (const auto &row : sqlRes) { res.emplace_back(row[0], row[1]); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createEllipsoidFromExisting( const datum::EllipsoidNNPtr &ellipsoid) const { std::string sql( "SELECT auth_name, code FROM ellipsoid WHERE " "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND " "((semi_minor_axis IS NOT NULL AND " "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR " "((inv_flattening IS NOT NULL AND " "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))"); ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(), ellipsoid->computeSemiMinorAxis().getSIValue(), ellipsoid->computedInverseFlattening()}; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code)); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createGeodeticCRSFromDatum( const std::string &datum_auth_name, const std::string &datum_code, const std::string &geodetic_crs_type) const { std::string sql( "SELECT auth_name, code FROM geodetic_crs WHERE " "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); ListOfParams params{datum_auth_name, datum_code}; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } if (!geodetic_crs_type.empty()) { sql += " AND type = ?"; params.emplace_back(geodetic_crs_type); } sql += " ORDER BY auth_name, code"; auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createVerticalCRSFromDatum( const std::string &datum_auth_name, const std::string &datum_code) const { std::string sql( "SELECT auth_name, code FROM vertical_crs WHERE " "datum_auth_name = ? AND datum_code = ? AND deprecated = 0"); ListOfParams params{datum_auth_name, datum_code}; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createGeodeticCRSFromEllipsoid( const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code, const std::string &geodetic_crs_type) const { std::string sql( "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs " "JOIN geodetic_datum ON " "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND " "geodetic_crs.datum_code = geodetic_datum.code WHERE " "geodetic_datum.ellipsoid_auth_name = ? AND " "geodetic_datum.ellipsoid_code = ? AND " "geodetic_datum.deprecated = 0 AND " "geodetic_crs.deprecated = 0"); ListOfParams params{ellipsoid_auth_name, ellipsoid_code}; if (d->hasAuthorityRestriction()) { sql += " AND geodetic_crs.auth_name = ?"; params.emplace_back(d->authority()); } if (!geodetic_crs_type.empty()) { sql += " AND geodetic_crs.type = ?"; params.emplace_back(geodetic_crs_type); } auto sqlRes = d->run(sql, params); std::list res; for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress static std::string buildSqlLookForAuthNameCode( const std::list> &list, ListOfParams ¶ms, const char *prefixField) { std::string sql("("); std::set authorities; for (const auto &crs : list) { auto boundCRS = dynamic_cast(crs.first.get()); const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() : crs.first->identifiers(); if (!ids.empty()) { authorities.insert(*(ids[0]->codeSpace())); } } bool firstAuth = true; for (const auto &auth_name : authorities) { if (!firstAuth) { sql += " OR "; } firstAuth = false; sql += "( "; sql += prefixField; sql += "auth_name = ? AND "; sql += prefixField; sql += "code IN ("; params.emplace_back(auth_name); bool firstGeodCRSForAuth = true; for (const auto &crs : list) { auto boundCRS = dynamic_cast(crs.first.get()); const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers() : crs.first->identifiers(); if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) { if (!firstGeodCRSForAuth) { sql += ','; } firstGeodCRSForAuth = false; sql += '?'; params.emplace_back(ids[0]->code()); } } sql += "))"; } sql += ')'; return sql; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::list AuthorityFactory::createProjectedCRSFromExisting( const crs::ProjectedCRSNNPtr &crs) const { std::list res; const auto &conv = crs->derivingConversionRef(); const auto &method = conv->method(); const auto methodEPSGCode = method->getEPSGCode(); if (methodEPSGCode == 0) { return res; } auto lockedThisFactory(d->getSharedFromThis()); assert(lockedThisFactory); const auto &baseCRS(crs->baseCRS()); auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory); auto geogCRS = dynamic_cast(baseCRS.get()); if (geogCRS) { const auto axisOrder = geogCRS->coordinateSystem()->axisOrder(); if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH || axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) { const auto &unit = geogCRS->coordinateSystem()->axisList()[0]->unit(); auto otherOrderGeogCRS = crs::GeographicCRS::create( util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, geogCRS->nameStr()), geogCRS->datum(), geogCRS->datumEnsemble(), axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ? cs::EllipsoidalCS::createLatitudeLongitude(unit) : cs::EllipsoidalCS::createLongitudeLatitude(unit)); auto otherCandidatesGeodCRS = otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory); candidatesGeodCRS.insert(candidatesGeodCRS.end(), otherCandidatesGeodCRS.begin(), otherCandidatesGeodCRS.end()); } } std::string sql( "SELECT projected_crs.auth_name, projected_crs.code FROM projected_crs " "JOIN conversion_table conv ON " "projected_crs.conversion_auth_name = conv.auth_name AND " "projected_crs.conversion_code = conv.code WHERE " "projected_crs.deprecated = 0 AND "); ListOfParams params; if (!candidatesGeodCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, "projected_crs.geodetic_crs_"); sql += " AND "; } sql += "conv.method_auth_name = 'EPSG' AND " "conv.method_code = ?"; params.emplace_back(toString(methodEPSGCode)); if (d->hasAuthorityRestriction()) { sql += " AND projected_crs.auth_name = ?"; params.emplace_back(d->authority()); } int iParam = 0; bool hasLat1stStd = false; double lat1stStd = 0; int iParamLat1stStd = 0; bool hasLat2ndStd = false; double lat2ndStd = 0; int iParamLat2ndStd = 0; for (const auto &genOpParamvalue : conv->parameterValues()) { iParam++; auto opParamvalue = dynamic_cast( genOpParamvalue.get()); if (!opParamvalue) { break; } const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); const auto ¶meterValue = opParamvalue->parameterValue(); if (!(paramEPSGCode > 0 && parameterValue->type() == operation::ParameterValue::Type::MEASURE)) { break; } const auto &measure = parameterValue->value(); const auto &unit = measure.unit(); if (unit == common::UnitOfMeasure::DEGREE && geogCRS->coordinateSystem()->axisList()[0]->unit() == unit) { if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { // Special case for standard parallels of LCC_2SP. See below if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) { hasLat1stStd = true; lat1stStd = measure.value(); iParamLat1stStd = iParam; continue; } else if (paramEPSGCode == EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { hasLat2ndStd = true; lat2ndStd = measure.value(); iParamLat2ndStd = iParam; continue; } } const auto iParamAsStr(toString(iParam)); sql += " AND conv.param"; sql += iParamAsStr; sql += "_code = ? AND conv.param"; sql += iParamAsStr; sql += "_auth_name = 'EPSG' AND conv.param"; sql += iParamAsStr; sql += "_value BETWEEN ? AND ?"; // As angles might be expressed with the odd unit EPSG:9110 // "sexagesimal DMS", we have to provide a broad range params.emplace_back(toString(paramEPSGCode)); params.emplace_back(measure.value() - 1); params.emplace_back(measure.value() + 1); } } // Special case for standard parallels of LCC_2SP: they can be switched if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && hasLat1stStd && hasLat2ndStd) { const auto iParam1AsStr(toString(iParamLat1stStd)); const auto iParam2AsStr(toString(iParamLat2ndStd)); sql += " AND conv.param"; sql += iParam1AsStr; sql += "_code = ? AND conv.param"; sql += iParam1AsStr; sql += "_auth_name = 'EPSG' AND conv.param"; sql += iParam2AsStr; sql += "_code = ? AND conv.param"; sql += iParam2AsStr; sql += "_auth_name = 'EPSG' AND (("; params.emplace_back( toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)); params.emplace_back( toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)); double val1 = lat1stStd; double val2 = lat2ndStd; for (int i = 0; i < 2; i++) { if (i == 1) { sql += ") OR ("; std::swap(val1, val2); } sql += "conv.param"; sql += iParam1AsStr; sql += "_value BETWEEN ? AND ? AND conv.param"; sql += iParam2AsStr; sql += "_value BETWEEN ? AND ?"; params.emplace_back(val1 - 1); params.emplace_back(val1 + 1); params.emplace_back(val2 - 1); params.emplace_back(val2 + 1); } sql += "))"; } auto sqlRes = d->run(sql, params); params.clear(); sql = "SELECT auth_name, code FROM projected_crs WHERE " "deprecated = 0 AND conversion_auth_name IS NULL AND "; if (!candidatesGeodCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params, "geodetic_crs_"); sql += " AND "; } const auto escapeLikeStr = [](const std::string &str) { return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"), "%", "\\%"); }; const auto ellpsSemiMajorStr = toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10); sql += "(text_definition LIKE ? ESCAPE '\\'"; // WKT2 definition { std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(method->nameStr()); patternVal += '%'; params.emplace_back(patternVal); } const auto *mapping = getMapping(method.get()); if (mapping && mapping->proj_name_main) { sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?"; std::string patternVal("%"); patternVal += "proj="; patternVal += mapping->proj_name_main; patternVal += '%'; params.emplace_back(patternVal); // could be a= or R= patternVal = "%="; patternVal += ellpsSemiMajorStr; patternVal += '%'; params.emplace_back(patternVal); std::string projEllpsName; std::string ellpsName; if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName, ellpsName)) { sql += " OR text_definition LIKE ?"; // Could be ellps= or datum= patternVal = "%="; patternVal += projEllpsName; patternVal += '%'; params.emplace_back(patternVal); } sql += "))"; } // WKT1_GDAL definition const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName(); if (wkt1GDALMethodName) { sql += " OR text_definition LIKE ? ESCAPE '\\'"; std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(wkt1GDALMethodName); patternVal += '%'; params.emplace_back(patternVal); } // WKT1_ESRI definition const char *esriMethodName = conv->getESRIMethodName(); if (esriMethodName) { sql += " OR text_definition LIKE ? ESCAPE '\\'"; std::string patternVal("%"); patternVal += ','; patternVal += ellpsSemiMajorStr; patternVal += '%'; patternVal += escapeLikeStr(esriMethodName); patternVal += '%'; auto fe = &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING); if (*fe == Measure()) { fe = &conv->parameterValueMeasure( EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN); } if (!(*fe == Measure())) { patternVal += "PARAMETER[\"False\\_Easting\","; patternVal += toString(fe->convertToUnit( crs->coordinateSystem()->axisList()[0]->unit()), 10); patternVal += '%'; } auto lat = &conv->parameterValueMeasure( EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN); if (*lat == Measure()) { lat = &conv->parameterValueMeasure( EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN); } if (!(*lat == Measure())) { patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\","; const auto &angularUnit = dynamic_cast(crs->baseCRS().get()) ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit() : UnitOfMeasure::DEGREE; patternVal += toString(lat->convertToUnit(angularUnit), 10); patternVal += '%'; } params.emplace_back(patternVal); } sql += ")"; if (d->hasAuthorityRestriction()) { sql += " AND auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes2 = d->run(sql, params); if (sqlRes.size() <= 200) { for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back( d->createFactory(auth_name)->createProjectedCRS(code)); } } if (sqlRes2.size() <= 200) { for (const auto &row : sqlRes2) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back( d->createFactory(auth_name)->createProjectedCRS(code)); } } return res; } // --------------------------------------------------------------------------- std::list AuthorityFactory::createCompoundCRSFromExisting( const crs::CompoundCRSNNPtr &crs) const { std::list res; auto lockedThisFactory(d->getSharedFromThis()); assert(lockedThisFactory); const auto &components = crs->componentReferenceSystems(); if (components.size() != 2) { return res; } auto candidatesHorizCRS = components[0]->identify(lockedThisFactory); auto candidatesVertCRS = components[1]->identify(lockedThisFactory); if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) { return res; } std::string sql("SELECT auth_name, code FROM compound_crs WHERE " "deprecated = 0 AND "); ListOfParams params; bool addAnd = false; if (!candidatesHorizCRS.empty()) { sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params, "horiz_crs_"); addAnd = true; } if (!candidatesVertCRS.empty()) { if (addAnd) { sql += " AND "; } sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params, "vertical_crs_"); addAnd = true; } if (d->hasAuthorityRestriction()) { if (addAnd) { sql += " AND "; } sql += "auth_name = ?"; params.emplace_back(d->authority()); } auto sqlRes = d->run(sql, params); for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code)); } return res; } // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress std::vector AuthorityFactory::getTransformationsForGeoid( const std::string &geoidName, bool usePROJAlternativeGridNames) const { std::vector res; const std::string sql("SELECT operation_auth_name, operation_code FROM " "geoid_model WHERE name = ?"); auto sqlRes = d->run(sql, {geoidName}); for (const auto &row : sqlRes) { const auto &auth_name = row[0]; const auto &code = row[1]; res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation( code, usePROJAlternativeGridNames)); } return res; } //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress FactoryException::FactoryException(const char *message) : Exception(message) {} // --------------------------------------------------------------------------- FactoryException::FactoryException(const std::string &message) : Exception(message) {} // --------------------------------------------------------------------------- FactoryException::~FactoryException() = default; // --------------------------------------------------------------------------- FactoryException::FactoryException(const FactoryException &) = default; //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress struct NoSuchAuthorityCodeException::Private { std::string authority_; std::string code_; Private(const std::string &authority, const std::string &code) : authority_(authority), code_(code) {} }; // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( const std::string &message, const std::string &authority, const std::string &code) : FactoryException(message), d(internal::make_unique(authority, code)) {} // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default; // --------------------------------------------------------------------------- NoSuchAuthorityCodeException::NoSuchAuthorityCodeException( const NoSuchAuthorityCodeException &other) : FactoryException(other), d(internal::make_unique(*(other.d))) {} //! @endcond // --------------------------------------------------------------------------- /** \brief Returns authority name. */ const std::string &NoSuchAuthorityCodeException::getAuthority() const { return d->authority_; } // --------------------------------------------------------------------------- /** \brief Returns authority code. */ const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const { return d->code_; } // --------------------------------------------------------------------------- } // namespace io NS_PROJ_END