diff options
Diffstat (limited to 'src/iso19111/factory.cpp')
| -rw-r--r-- | src/iso19111/factory.cpp | 1514 |
1 files changed, 1500 insertions, 14 deletions
diff --git a/src/iso19111/factory.cpp b/src/iso19111/factory.cpp index fbe88dda..36ef217d 100644 --- a/src/iso19111/factory.cpp +++ b/src/iso19111/factory.cpp @@ -52,6 +52,7 @@ #include <cmath> #include <cstdlib> #include <cstring> +#include <functional> #include <iomanip> #include <limits> #include <locale> @@ -101,15 +102,20 @@ constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1; // must be incremented. constexpr int DATABASE_LAYOUT_VERSION_MINOR = 0; +constexpr size_t N_MAX_PARAMS = 7; + // --------------------------------------------------------------------------- struct SQLValues { - enum class Type { STRING, DOUBLE }; + enum class Type { STRING, INT, DOUBLE }; // cppcheck-suppress noExplicitConstructor SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {} // cppcheck-suppress noExplicitConstructor + SQLValues(int value) : type_(Type::INT), int_(value) {} + + // cppcheck-suppress noExplicitConstructor SQLValues(double value) : type_(Type::DOUBLE), double_(value) {} const Type &type() const { return type_; } @@ -118,11 +124,15 @@ struct SQLValues { const std::string &stringValue() const { return str_; } // cppcheck-suppress functionStatic + int intValue() const { return int_; } + + // cppcheck-suppress functionStatic double doubleValue() const { return double_; } private: Type type_; std::string str_{}; + int int_ = 0; double double_ = 0.0; }; @@ -255,7 +265,12 @@ struct DatabaseContext::Private { private: friend class DatabaseContext; + // This is a manual implementation of std::enable_shared_from_this<> that + // avoids publicly deriving from it. + std::weak_ptr<DatabaseContext> self_{}; + std::string databasePath_{}; + std::vector<std::string> auxiliaryDatabasePaths_{}; bool close_handle_ = true; sqlite3 *sqlite_handle_{}; std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{}; @@ -265,6 +280,10 @@ struct DatabaseContext::Private { std::string lastMetadataValue_{}; std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{}; + // Used by startInsertStatementsSession() and related functions + std::string memoryDbForInsertPath_{}; + sqlite3 *memoryDbHandle_ = nullptr; + using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>; static constexpr size_t CACHE_SIZE = 128; @@ -296,9 +315,84 @@ struct DatabaseContext::Private { void closeDB() noexcept; + void clearCaches(); + // cppcheck-suppress functionStatic void registerFunctions(); + std::string findFreeCode(const std::string &tableName, + const std::string &authName, + const std::string &codePrototype); + + void identify(const DatabaseContextNNPtr &dbContext, + const cs::CoordinateSystemNNPtr &obj, std::string &authName, + std::string &code); + void identifyOrInsert(const DatabaseContextNNPtr &dbContext, + const cs::CoordinateSystemNNPtr &obj, + const std::string &ownerType, + const std::string &ownerAuthName, + const std::string &ownerCode, std::string &authName, + std::string &code, + std::vector<std::string> &sqlStatements); + + void identify(const DatabaseContextNNPtr &dbContext, + const common::UnitOfMeasure &obj, std::string &authName, + std::string &code); + void identifyOrInsert(const DatabaseContextNNPtr &dbContext, + const common::UnitOfMeasure &unit, + const std::string &ownerAuthName, + std::string &authName, std::string &code, + std::vector<std::string> &sqlStatements); + + void appendSql(std::vector<std::string> &sqlStatements, + const std::string &sql); + + void identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj, + const std::string &tableName, + const std::string &authName, + const std::string &code, + std::vector<std::string> &sqlStatements); + + std::vector<std::string> + getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs, + const std::string &authName, const std::string &code, + bool numericCode); + + std::vector<std::string> + getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs, + const std::string &authName, const std::string &code, + bool numericCode); + #ifdef ENABLE_CUSTOM_LOCKLESS_VFS std::unique_ptr<SQLite3VFS> vfs_{}; #endif @@ -355,6 +449,24 @@ void DatabaseContext::Private::closeDB() noexcept { // --------------------------------------------------------------------------- +void DatabaseContext::Private::clearCaches() { + + cacheUOM_.clear(); + cacheCRS_.clear(); + cacheEllipsoid_.clear(); + cacheGeodeticDatum_.clear(); + cacheDatumEnsemble_.clear(); + cachePrimeMeridian_.clear(); + cacheCS_.clear(); + cacheExtent_.clear(); + cacheCRSToCrsCoordOp_.clear(); + cacheGridInfo_.clear(); + cacheAllowedAuthorities_.clear(); + cacheAliasNames_.clear(); +} + +// --------------------------------------------------------------------------- + void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache, const std::string &code, const util::BaseObjectPtr &obj) { @@ -684,6 +796,10 @@ void DatabaseContext::Private::attachExtraDatabases( } closeDB(); + if (auxiliaryDatabasePaths.empty()) { + open(databasePath_, pjCtxt()); + return; + } sqlite3_open_v2( ":memory:", &sqlite_handle_, @@ -850,13 +966,16 @@ SQLResultSet DatabaseContext::Private::run(const std::string &sql, int nBindField = 1; for (const auto ¶m : parameters) { - if (param.type() == SQLValues::Type::STRING) { + const auto paramType = param.type(); + if (paramType == SQLValues::Type::STRING) { auto strValue = param.stringValue(); sqlite3_bind_text(stmt, nBindField, strValue.c_str(), static_cast<int>(strValue.size()), SQLITE_TRANSIENT); + } else if (paramType == SQLValues::Type::INT) { + sqlite3_bind_int(stmt, nBindField, param.intValue()); } else { - assert(param.type() == SQLValues::Type::DOUBLE); + assert(paramType == SQLValues::Type::DOUBLE); sqlite3_bind_double(stmt, nBindField, param.doubleValue()); } nBindField++; @@ -869,8 +988,11 @@ SQLResultSet DatabaseContext::Private::run(const std::string &sql, nPos = sqlSubst.find('?', nPos); assert(nPos != std::string::npos); std::string strValue; - if (param.type() == SQLValues::Type::STRING) { + const auto paramType = param.type(); + if (paramType == SQLValues::Type::STRING) { strValue = '\'' + param.stringValue() + '\''; + } else if (paramType == SQLValues::Type::INT) { + strValue = toString(param.intValue()); } else { strValue = toString(param.doubleValue()); } @@ -915,12 +1037,1117 @@ SQLResultSet DatabaseContext::Private::run(const std::string &sql, return result; } +// --------------------------------------------------------------------------- + +static std::string formatStatement(const char *fmt, ...) { + std::string res; + va_list args; + va_start(args, fmt); + for (int i = 0; fmt[i] != '\0'; ++i) { + if (fmt[i] == '%') { + if (fmt[i + 1] == '%') { + res += '%'; + } else if (fmt[i + 1] == 'q') { + const char *arg = va_arg(args, const char *); + for (int j = 0; arg[j] != '\0'; ++j) { + if (arg[j] == '\'') + res += arg[j]; + res += arg[j]; + } + } else if (fmt[i + 1] == 's') { + const char *arg = va_arg(args, const char *); + res += arg; + } else if (fmt[i + 1] == 'f') { + const double arg = va_arg(args, double); + res += toString(arg); + } else if (fmt[i + 1] == 'd') { + const int arg = va_arg(args, int); + res += toString(arg); + } else { + va_end(args); + throw FactoryException( + "Unsupported formatter in formatStatement()"); + } + ++i; + } else { + res += fmt[i]; + } + } + va_end(args); + return res; +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::appendSql( + std::vector<std::string> &sqlStatements, const std::string &sql) { + sqlStatements.emplace_back(sql); + char *errMsg = nullptr; + if (sqlite3_exec(memoryDbHandle_, sql.c_str(), nullptr, nullptr, &errMsg) != + SQLITE_OK) { + std::string s("Cannot execute " + sql); + if (errMsg) { + s += " : "; + s += errMsg; + } + sqlite3_free(errMsg); + throw FactoryException(s); + } + sqlite3_free(errMsg); +} + +// --------------------------------------------------------------------------- + +static void identifyFromNameOrCode( + const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const common::IdentifiedObjectNNPtr &obj, + std::function<std::shared_ptr<util::IComparable>( + const AuthorityFactoryNNPtr &authFactory, const std::string &)> + instantiateFunc, + AuthorityFactory::ObjectType objType, std::string &authName, + std::string &code) { + for (const auto &id : obj->identifiers()) { + try { + const auto tmpAuthFactory = + AuthorityFactory::create(dbContext, *(id->codeSpace())); + if (instantiateFunc(tmpAuthFactory, id->code()) + ->isEquivalentTo( + obj.get(), util::IComparable::Criterion::EQUIVALENT)) { + authName = *(id->codeSpace()); + code = id->code(); + return; + } + } catch (const std::exception &) { + } + } + + const auto candidates = allAuthFactory->createObjectsFromName( + obj->nameStr(), {objType}, false, 0); + for (const auto &candidate : candidates) { + const auto &ids = candidate->identifiers(); + if (!ids.empty() && + candidate->isEquivalentTo( + obj.get(), util::IComparable::Criterion::EQUIVALENT)) { + const auto &id = ids.front(); + authName = *(id->codeSpace()); + code = id->code(); + } + } +} + +// --------------------------------------------------------------------------- + +static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const datum::DatumEnsembleNNPtr &obj, + std::string &authName, std::string &code) { + const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, + const std::string &lCode) { + return util::nn_static_pointer_cast<util::IComparable>( + authFactory->createDatumEnsemble(lCode, "geodetic_datum")); + }; + identifyFromNameOrCode(dbContext, allAuthFactory, obj, instantiateFunc, + AuthorityFactory::ObjectType::DATUM_ENSEMBLE, + authName, code); +} + +// --------------------------------------------------------------------------- + +static void +identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const datum::GeodeticReferenceFrameNNPtr &obj, + std::string &authName, std::string &code) { + const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, + const std::string &lCode) { + return util::nn_static_pointer_cast<util::IComparable>( + authFactory->createGeodeticDatum(lCode)); + }; + identifyFromNameOrCode( + dbContext, allAuthFactory, obj, instantiateFunc, + AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code); +} + +// --------------------------------------------------------------------------- + +static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const datum::EllipsoidNNPtr &obj, + std::string &authName, std::string &code) { + const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, + const std::string &lCode) { + return util::nn_static_pointer_cast<util::IComparable>( + authFactory->createEllipsoid(lCode)); + }; + identifyFromNameOrCode(dbContext, allAuthFactory, obj, instantiateFunc, + AuthorityFactory::ObjectType::ELLIPSOID, authName, + code); +} + +// --------------------------------------------------------------------------- + +static void identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const datum::PrimeMeridianNNPtr &obj, + std::string &authName, std::string &code) { + const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, + const std::string &lCode) { + return util::nn_static_pointer_cast<util::IComparable>( + authFactory->createPrimeMeridian(lCode)); + }; + identifyFromNameOrCode(dbContext, allAuthFactory, obj, instantiateFunc, + AuthorityFactory::ObjectType::PRIME_MERIDIAN, + authName, code); +} + +// --------------------------------------------------------------------------- + +static void +identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext, + const AuthorityFactoryNNPtr &allAuthFactory, + const datum::VerticalReferenceFrameNNPtr &obj, + std::string &authName, std::string &code) { + const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory, + const std::string &lCode) { + return util::nn_static_pointer_cast<util::IComparable>( + authFactory->createVerticalDatum(lCode)); + }; + identifyFromNameOrCode( + dbContext, allAuthFactory, obj, instantiateFunc, + AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code); +} + +// --------------------------------------------------------------------------- + +static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) { + if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) { + return "ellipsoidal"; + } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) { + return "Cartesian"; + } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) { + return "vertical"; + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +std::string +DatabaseContext::Private::findFreeCode(const std::string &tableName, + const std::string &authName, + const std::string &codePrototype) { + std::string code(codePrototype); + if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?", + {authName, code}) + .empty()) { + return code; + } + + for (int counter = 2; counter < 10; counter++) { + code = codePrototype + '_' + toString(counter); + if (run("SELECT 1 FROM " + tableName + + " WHERE auth_name = ? AND code = ?", + {authName, code}) + .empty()) { + return code; + } + } + + // shouldn't happen hopefully... + throw FactoryException("Cannot insert " + tableName + + ": too many similar codes"); +} + +// --------------------------------------------------------------------------- + +static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) { + switch (unit.type()) { + case common::UnitOfMeasure::Type::LINEAR: + return "length"; + + case common::UnitOfMeasure::Type::ANGULAR: + return "angle"; + + case common::UnitOfMeasure::Type::SCALE: + return "scale"; + + case common::UnitOfMeasure::Type::TIME: + return "time"; + + default: + break; + } + return nullptr; +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext, + const common::UnitOfMeasure &obj, + std::string &authName, + std::string &code) { + // Identify quickly a few well-known units + const double convFactor = obj.conversionToSI(); + switch (obj.type()) { + case common::UnitOfMeasure::Type::LINEAR: { + if (convFactor == 1.0) { + authName = "EPSG"; + code = "9001"; + return; + } + break; + } + case common::UnitOfMeasure::Type::ANGULAR: { + constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02; + if (std::abs(convFactor - CONV_FACTOR_DEGREE) <= + 1e-10 * CONV_FACTOR_DEGREE) { + authName = "EPSG"; + code = "9102"; + return; + } + break; + } + case common::UnitOfMeasure::Type::SCALE: { + if (convFactor == 1.0) { + authName = "EPSG"; + code = "9201"; + return; + } + break; + } + default: + break; + } + + std::string sql("SELECT auth_name, code FROM unit_of_measure " + "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor"); + ListOfParams params{convFactor}; + const char *type = getUnitDatabaseType(obj); + if (type) { + sql += " AND type = ?"; + params.emplace_back(std::string(type)); + } + sql += " ORDER BY auth_name, code"; + const auto res = run(sql, params); + for (const auto &row : res) { + const auto &rowAuthName = row[0]; + const auto &rowCode = row[1]; + const auto tmpAuthFactory = + AuthorityFactory::create(dbContext, rowAuthName); + try { + tmpAuthFactory->createUnitOfMeasure(rowCode); + authName = rowAuthName; + code = rowCode; + return; + } catch (const std::exception &) { + } + } +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::identifyOrInsert( + const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit, + const std::string &ownerAuthName, std::string &authName, std::string &code, + std::vector<std::string> &sqlStatements) { + authName = unit.codeSpace(); + code = unit.code(); + if (authName.empty()) { + identify(dbContext, unit, authName, code); + } + if (!authName.empty()) { + return; + } + const char *type = getUnitDatabaseType(unit); + if (type == nullptr) { + throw FactoryException("Cannot insert this type of UnitOfMeasure"); + } + + // Insert new record + authName = ownerAuthName; + const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_")); + code = findFreeCode("unit_of_measure", authName, codePrototype); + + const auto sql = formatStatement( + "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);", + authName.c_str(), code.c_str(), unit.name().c_str(), type, + unit.conversionToSI()); + appendSql(sqlStatements, sql); +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext, + const cs::CoordinateSystemNNPtr &obj, + std::string &authName, + std::string &code) { + + const auto &axisList = obj->axisList(); + if (axisList.size() == 1U && + axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) && + &(axisList[0]->direction()) == &cs::AxisDirection::UP && + (axisList[0]->nameStr() == "Up" || + axisList[0]->nameStr() == "Gravity-related height")) { + // preferred coordinate system for gravity-related height + authName = "EPSG"; + code = "6499"; + return; + } + + std::string sql( + "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?"); + ListOfParams params{static_cast<int>(axisList.size())}; + const char *type = getCSDatabaseType(obj); + if (type) { + sql += " AND type = ?"; + params.emplace_back(std::string(type)); + } + sql += " ORDER BY auth_name, code"; + const auto res = run(sql, params); + for (const auto &row : res) { + const auto &rowAuthName = row[0]; + const auto &rowCode = row[1]; + const auto tmpAuthFactory = + AuthorityFactory::create(dbContext, rowAuthName); + try { + const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode); + if (cs->_isEquivalentTo(obj.get(), + util::IComparable::Criterion::EQUIVALENT)) { + authName = rowAuthName; + code = rowCode; + if (authName == "EPSG" && code == "4400") { + // preferred coordinate system for cartesian + // Easting, Northing + return; + } + if (authName == "EPSG" && code == "6422") { + // preferred coordinate system for geographic lat, lon + return; + } + if (authName == "EPSG" && code == "6423") { + // preferred coordinate system for geographic lat, lon, h + return; + } + } + } catch (const std::exception &) { + } + } +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::identifyOrInsert( + const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj, + const std::string &ownerType, const std::string &ownerAuthName, + const std::string &ownerCode, std::string &authName, std::string &code, + std::vector<std::string> &sqlStatements) { + + identify(dbContext, obj, authName, code); + if (!authName.empty()) { + return; + } + + const char *type = getCSDatabaseType(obj); + if (type == nullptr) { + throw FactoryException("Cannot insert this type of CoordinateSystem"); + } + + // Insert new record in coordinate_system + authName = ownerAuthName; + const std::string codePrototype("CS_" + ownerType + '_' + ownerCode); + code = findFreeCode("coordinate_system", authName, codePrototype); + + const auto &axisList = obj->axisList(); + { + const auto sql = formatStatement( + "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);", + authName.c_str(), code.c_str(), type, + static_cast<int>(axisList.size())); + appendSql(sqlStatements, sql); + } + + // Insert new records for the axis + for (int i = 0; i < static_cast<int>(axisList.size()); ++i) { + const auto &axis = axisList[i]; + std::string uomAuthName; + std::string uomCode; + identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName, + uomCode, sqlStatements); + const auto sql = formatStatement( + "INSERT INTO axis VALUES(" + "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');", + authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(), + axis->nameStr().c_str(), axis->abbreviation().c_str(), + axis->direction().toString().c_str(), authName.c_str(), + code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str()); + appendSql(sqlStatements, sql); + } +} + +// --------------------------------------------------------------------------- + +void DatabaseContext::Private::identifyOrInsertUsages( + const common::ObjectUsageNNPtr &obj, const std::string &tableName, + const std::string &authName, const std::string &code, + std::vector<std::string> &sqlStatements) { + + std::string usageCode("USAGE_"); + const std::string upperTableName(toupper(tableName)); + if (!starts_with(code, upperTableName)) { + usageCode += upperTableName; + usageCode += '_'; + } + usageCode += code; + + const auto &domains = obj->domains(); + if (domains.empty()) { + const auto sql = + formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q'," + "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');", + authName.c_str(), usageCode.c_str(), + tableName.c_str(), authName.c_str(), code.c_str()); + appendSql(sqlStatements, sql); + return; + } + + int usageCounter = 1; + for (const auto &domain : domains) { + std::string scopeAuthName; + std::string scopeCode; + const auto &scope = domain->scope(); + if (scope.has_value()) { + const auto rows = + run("SELECT auth_name, code, " + "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) " + "AS order_idx " + "FROM scope WHERE scope = ? AND deprecated = 0 " + "ORDER BY order_idx", + {*scope}); + if (!rows.empty()) { + const auto &row = rows.front(); + scopeAuthName = row[0]; + scopeCode = row[1]; + } else { + scopeAuthName = authName; + scopeCode = "SCOPE_" + tableName + "_" + code; + const auto sql = formatStatement( + "INSERT INTO scope VALUES('%q','%q','%q',0);", + scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str()); + appendSql(sqlStatements, sql); + } + } else { + scopeAuthName = "PROJ"; + scopeCode = "SCOPE_UNKNOWN"; + } + + std::string extentAuthName("PROJ"); + std::string extentCode("EXTENT_UNKNOWN"); + const auto &extent = domain->domainOfValidity(); + if (extent) { + const auto &geogElts = extent->geographicElements(); + if (!geogElts.empty()) { + const auto bbox = + dynamic_cast<const metadata::GeographicBoundingBox *>( + geogElts.front().get()); + if (bbox) { + const auto rows = run( + "SELECT auth_name, code, " + "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) " + "AS order_idx " + "FROM extent WHERE south_lat = ? AND north_lat = ? " + "AND west_lon = ? AND east_lon = ? AND deprecated = 0 " + "ORDER BY order_idx", + {bbox->southBoundLatitude(), bbox->northBoundLatitude(), + bbox->westBoundLongitude(), + bbox->eastBoundLongitude()}); + if (!rows.empty()) { + const auto &row = rows.front(); + extentAuthName = row[0]; + extentCode = row[1]; + } else { + extentAuthName = authName; + extentCode = "EXTENT_" + tableName + "_" + code; + std::string description(*(extent->description())); + if (description.empty()) { + description = "unknown"; + } + const auto sql = formatStatement( + "INSERT INTO extent " + "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);", + extentAuthName.c_str(), extentCode.c_str(), + description.c_str(), description.c_str(), + bbox->southBoundLatitude(), + bbox->northBoundLatitude(), + bbox->westBoundLongitude(), + bbox->eastBoundLongitude()); + appendSql(sqlStatements, sql); + } + } + } + } + + if (domains.size() > 1) { + usageCode += '_'; + usageCode += toString(usageCounter); + } + const auto sql = formatStatement( + "INSERT INTO usage VALUES('%q','%q','%q','%q','%q'," + "'%q','%q','%q','%q');", + authName.c_str(), usageCode.c_str(), tableName.c_str(), + authName.c_str(), code.c_str(), extentAuthName.c_str(), + extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str()); + appendSql(sqlStatements, sql); + + usageCounter++; + } +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const datum::PrimeMeridianNNPtr &pm, const std::string &authName, + const std::string &code, bool /*numericCode*/) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + // Check if the object is already known under that code + std::string pmAuthName; + std::string pmCode; + identifyFromNameOrCode(self, allAuthFactory, pm, pmAuthName, pmCode); + if (pmAuthName == authName && pmCode == code) { + return {}; + } + + std::vector<std::string> sqlStatements; + + // Insert new record in prime_meridian table + std::string uomAuthName; + std::string uomCode; + identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName, + uomCode, sqlStatements); + + const auto sql = formatStatement( + "INSERT INTO prime_meridian VALUES(" + "'%q','%q','%q',%f,'%q','%q',0);", + authName.c_str(), code.c_str(), pm->nameStr().c_str(), + pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str()); + appendSql(sqlStatements, sql); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName, + const std::string &code, bool /*numericCode*/) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + // Check if the object is already known under that code + std::string ellipsoidAuthName; + std::string ellipsoidCode; + identifyFromNameOrCode(self, allAuthFactory, ellipsoid, ellipsoidAuthName, + ellipsoidCode); + if (ellipsoidAuthName == authName && ellipsoidCode == code) { + return {}; + } + + std::vector<std::string> sqlStatements; + + // Find or insert celestial body + const auto &semiMajorAxis = ellipsoid->semiMajorAxis(); + const double semiMajorAxisMetre = semiMajorAxis.getSIValue(); + constexpr double tolerance = 0.005; + std::string bodyAuthName; + std::string bodyCode; + auto res = run("SELECT auth_name, code, " + "(ABS(semi_major_axis - ?) / semi_major_axis ) " + "AS rel_error FROM celestial_body WHERE rel_error <= ?", + {semiMajorAxisMetre, tolerance}); + if (!res.empty()) { + const auto &row = res.front(); + bodyAuthName = row[0]; + bodyCode = row[1]; + } else { + bodyAuthName = authName; + bodyCode = "BODY_" + code; + const auto bodyName = "Body of " + ellipsoid->nameStr(); + const auto sql = formatStatement( + "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);", + bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(), + semiMajorAxisMetre); + appendSql(sqlStatements, sql); + } + + // Insert new record in ellipsoid table + std::string uomAuthName; + std::string uomCode; + identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode, + sqlStatements); + std::string invFlattening("NULL"); + std::string semiMinorAxis("NULL"); + if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) { + semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value()); + } else { + invFlattening = toString(ellipsoid->computedInverseFlattening()); + } + + const auto sql = formatStatement( + "INSERT INTO ellipsoid VALUES(" + "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);", + authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(), + "", // description + bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(), + uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(), + semiMinorAxis.c_str()); + appendSql(sqlStatements, sql); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const datum::GeodeticReferenceFrameNNPtr &datum, + const std::string &authName, const std::string &code, bool numericCode) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + // Check if the object is already known under that code + std::string datumAuthName; + std::string datumCode; + identifyFromNameOrCode(self, allAuthFactory, datum, datumAuthName, + datumCode); + if (datumAuthName == authName && datumCode == code) { + return {}; + } + + std::vector<std::string> sqlStatements; + + // Find or insert ellipsoid + std::string ellipsoidAuthName; + std::string ellipsoidCode; + const auto &ellipsoidOfDatum = datum->ellipsoid(); + identifyFromNameOrCode(self, allAuthFactory, ellipsoidOfDatum, + ellipsoidAuthName, ellipsoidCode); + if (ellipsoidAuthName.empty()) { + ellipsoidAuthName = authName; + if (numericCode) { + ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum, + ellipsoidAuthName, true); + } else { + ellipsoidCode = "ELLPS_" + code; + } + sqlStatements = self->getInsertStatementsFor( + ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode); + } + + // Find or insert prime meridian + std::string pmAuthName; + std::string pmCode; + const auto &pmOfDatum = datum->primeMeridian(); + identifyFromNameOrCode(self, allAuthFactory, pmOfDatum, pmAuthName, pmCode); + if (pmAuthName.empty()) { + pmAuthName = authName; + if (numericCode) { + pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true); + } else { + pmCode = "PM_" + code; + } + const auto sqlStatementsTmp = self->getInsertStatementsFor( + pmOfDatum, pmAuthName, pmCode, numericCode); + sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(), + sqlStatementsTmp.end()); + } + + // Insert new record in geodetic_datum table + const auto sql = formatStatement( + "INSERT INTO geodetic_datum VALUES(" + "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,NULL,0);", + authName.c_str(), code.c_str(), datum->nameStr().c_str(), + "", // description + ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(), + pmCode.c_str()); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(datum, "geodetic_datum", authName, code, + sqlStatements); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const crs::GeodeticCRSNNPtr &crs, const std::string &authName, + const std::string &code, bool numericCode) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + std::vector<std::string> sqlStatements; + + // Find or insert datum/datum ensemble + std::string datumAuthName; + std::string datumCode; + const auto &ensemble = crs->datumEnsemble(); + if (ensemble) { + identifyFromNameOrCode(self, allAuthFactory, NN_NO_CHECK(ensemble), + datumAuthName, datumCode); + if (datumAuthName.empty()) { + throw FactoryException( + "Unhandled yet: insertion of new DatumEnsemble"); + } + } else { + const auto &datum = crs->datum(); + assert(datum); + const auto datumNN = NN_NO_CHECK(datum); + identifyFromNameOrCode(self, allAuthFactory, datumNN, datumAuthName, + datumCode); + if (datumAuthName.empty()) { + datumAuthName = authName; + if (numericCode) { + datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true); + } else { + datumCode = "GEODETIC_DATUM_" + code; + } + sqlStatements = self->getInsertStatementsFor( + datumNN, datumAuthName, datumCode, numericCode); + } + } + + // Find or insert coordinate system + const auto &coordinateSystem = crs->coordinateSystem(); + std::string csAuthName; + std::string csCode; + identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code, + csAuthName, csCode, sqlStatements); + + const char *type = GEOG_2D; + if (coordinateSystem->axisList().size() == 3) { + if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) { + type = GEOG_3D; + } else { + type = GEOCENTRIC; + } + } + + // Insert new record in geodetic_crs table + const auto sql = + formatStatement("INSERT INTO geodetic_crs VALUES(" + "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);", + authName.c_str(), code.c_str(), crs->nameStr().c_str(), + "", // description + type, csAuthName.c_str(), csCode.c_str(), + datumAuthName.c_str(), datumCode.c_str()); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(crs, "geodetic_crs", authName, code, sqlStatements); + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const crs::ProjectedCRSNNPtr &crs, const std::string &authName, + const std::string &code, bool numericCode) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + std::vector<std::string> sqlStatements; + + // Find or insert base geodetic CRS + const auto &baseCRS = crs->baseCRS(); + std::string geodAuthName; + std::string geodCode; + const auto candidates = baseCRS->identify(allAuthFactory); + for (const auto &candidate : candidates) { + if (candidate.second == 100) { + const auto &ids = candidate.first->identifiers(); + for (const auto &id : ids) { + geodAuthName = *(id->codeSpace()); + geodCode = id->code(); + break; + } + } + } + if (geodAuthName.empty()) { + geodAuthName = authName; + geodCode = "GEODETIC_CRS_" + code; + sqlStatements = self->getInsertStatementsFor(baseCRS, geodAuthName, + geodCode, numericCode); + } + + // Insert new record in conversion table + const auto &conversion = crs->derivingConversionRef(); + std::string convAuthName(authName); + std::string convCode("CONVERSION_" + code); + if (numericCode) { + convCode = self->suggestsCodeFor(conversion, convAuthName, true); + } + { + const auto &method = conversion->method(); + const auto &methodIds = method->identifiers(); + if (methodIds.empty()) { + throw FactoryException( + "Cannot insert projection with method without identifier"); + } + const auto &methodId = methodIds.front(); + const auto &methodAuthName = *(methodId->codeSpace()); + const auto &methodCode = methodId->code(); + auto sql = formatStatement("INSERT INTO conversion VALUES(" + "'%q','%q','%q','','%q','%q','%q'", + convAuthName.c_str(), convCode.c_str(), + conversion->nameStr().c_str(), + methodAuthName.c_str(), methodCode.c_str(), + method->nameStr().c_str()); + const auto &values = conversion->parameterValues(); + if (values.size() > N_MAX_PARAMS) { + throw FactoryException("Cannot insert projection with more than " + + toString(static_cast<int>(N_MAX_PARAMS)) + + " parameters"); + } + for (const auto &genOpParamvalue : values) { + auto opParamValue = + dynamic_cast<const operation::OperationParameterValue *>( + genOpParamvalue.get()); + if (!opParamValue) { + throw FactoryException("Cannot insert projection with " + "non-OperationParameterValue"); + } + const auto ¶m = opParamValue->parameter(); + const auto ¶mIds = param->identifiers(); + if (paramIds.empty()) { + throw FactoryException( + "Cannot insert projection with method parameter " + "without identifier"); + } + const auto ¶mId = paramIds.front(); + const auto ¶mAuthName = *(paramId->codeSpace()); + const auto ¶mCode = paramId->code(); + const auto &value = opParamValue->parameterValue()->value(); + const auto &unit = value.unit(); + std::string uomAuthName; + std::string uomCode; + identifyOrInsert(self, unit, authName, uomAuthName, uomCode, + sqlStatements); + sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'", + paramAuthName.c_str(), paramCode.c_str(), + param->nameStr().c_str(), value.value(), + uomAuthName.c_str(), uomCode.c_str()); + } + for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) { + sql += ",NULL,NULL,NULL,NULL,NULL,NULL"; + } + sql += ",0);"; + appendSql(sqlStatements, sql); + identifyOrInsertUsages(crs, "conversion", convAuthName, convCode, + sqlStatements); + } + + // Find or insert coordinate system + const auto &coordinateSystem = crs->coordinateSystem(); + std::string csAuthName; + std::string csCode; + identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code, + csAuthName, csCode, sqlStatements); + + // Insert new record in projected_crs table + const auto sql = formatStatement( + "INSERT INTO projected_crs VALUES(" + "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);", + authName.c_str(), code.c_str(), crs->nameStr().c_str(), + "", // description + csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(), + geodCode.c_str(), convAuthName.c_str(), convCode.c_str()); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(crs, "projected_crs", authName, code, sqlStatements); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const datum::VerticalReferenceFrameNNPtr &datum, + const std::string &authName, const std::string &code, + bool /* numericCode */) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + std::vector<std::string> sqlStatements; + + // Check if the object is already known under that code + std::string datumAuthName; + std::string datumCode; + identifyFromNameOrCode(self, allAuthFactory, datum, datumAuthName, + datumCode); + if (datumAuthName == authName && datumCode == code) { + return {}; + } + + // Insert new record in vertical_datum table + const auto sql = formatStatement("INSERT INTO vertical_datum VALUES(" + "'%q','%q','%q','%q',NULL,NULL,NULL,0);", + authName.c_str(), code.c_str(), + datum->nameStr().c_str(), + "" // description + ); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(datum, "vertical_datum", authName, code, + sqlStatements); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const crs::VerticalCRSNNPtr &crs, const std::string &authName, + const std::string &code, bool numericCode) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + std::vector<std::string> sqlStatements; + + // Find or insert datum/datum ensemble + std::string datumAuthName; + std::string datumCode; + const auto &ensemble = crs->datumEnsemble(); + if (ensemble) { + identifyFromNameOrCode(self, allAuthFactory, NN_NO_CHECK(ensemble), + datumAuthName, datumCode); + if (datumAuthName.empty()) { + throw FactoryException( + "Unhandled yet: insertion of new DatumEnsemble"); + } + } else { + const auto &datum = crs->datum(); + assert(datum); + const auto datumNN = NN_NO_CHECK(datum); + identifyFromNameOrCode(self, allAuthFactory, datumNN, datumAuthName, + datumCode); + if (datumAuthName.empty()) { + datumAuthName = authName; + if (numericCode) { + datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true); + } else { + datumCode = "VERTICAL_DATUM_" + code; + } + sqlStatements = self->getInsertStatementsFor( + datumNN, datumAuthName, datumCode, numericCode); + } + } + + // Find or insert coordinate system + const auto &coordinateSystem = crs->coordinateSystem(); + std::string csAuthName; + std::string csCode; + identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code, + csAuthName, csCode, sqlStatements); + + // Insert new record in vertical_crs table + const auto sql = + formatStatement("INSERT INTO vertical_crs VALUES(" + "'%q','%q','%q','%q','%q','%q','%q','%q',0);", + authName.c_str(), code.c_str(), crs->nameStr().c_str(), + "", // description + csAuthName.c_str(), csCode.c_str(), + datumAuthName.c_str(), datumCode.c_str()); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(crs, "vertical_crs", authName, code, sqlStatements); + + return sqlStatements; +} + +// --------------------------------------------------------------------------- + +std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor( + const crs::CompoundCRSNNPtr &crs, const std::string &authName, + const std::string &code, bool numericCode) { + + const auto self = NN_NO_CHECK(self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + + std::vector<std::string> sqlStatements; + + int counter = 1; + std::vector<std::pair<std::string, std::string>> componentsId; + const auto &components = crs->componentReferenceSystems(); + if (components.size() != 2) { + throw FactoryException( + "Cannot insert compound CRS with number of components != 2"); + } + for (const auto &component : components) { + std::string compAuthName; + std::string compCode; + const auto candidates = component->identify(allAuthFactory); + for (const auto &candidate : candidates) { + if (candidate.second == 100) { + const auto &ids = candidate.first->identifiers(); + for (const auto &id : ids) { + compAuthName = *(id->codeSpace()); + compCode = id->code(); + break; + } + } + } + if (compAuthName.empty()) { + compAuthName = authName; + if (numericCode) { + compCode = self->suggestsCodeFor(component, compAuthName, true); + } else { + compCode = "COMPONENT_" + code + '_' + toString(counter); + } + const auto sqlStatementsTmp = self->getInsertStatementsFor( + component, compAuthName, compCode, numericCode); + sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(), + sqlStatementsTmp.end()); + } + + componentsId.emplace_back( + std::pair<std::string, std::string>(compAuthName, compCode)); + + ++counter; + } + + // Insert new record in compound_crs table + const auto sql = formatStatement( + "INSERT INTO compound_crs VALUES(" + "'%q','%q','%q','%q','%q','%q','%q','%q',0);", + authName.c_str(), code.c_str(), crs->nameStr().c_str(), + "", // description + componentsId[0].first.c_str(), componentsId[0].second.c_str(), + componentsId[1].first.c_str(), componentsId[1].second.c_str()); + appendSql(sqlStatements, sql); + + identifyOrInsertUsages(crs, "compound_crs", authName, code, sqlStatements); + + return sqlStatements; +} + //! @endcond // --------------------------------------------------------------------------- //! @cond Doxygen_Suppress -DatabaseContext::~DatabaseContext() = default; +DatabaseContext::~DatabaseContext() { + try { + stopInsertStatementsSession(); + } catch (const std::exception &) { + } +} //! @endcond // --------------------------------------------------------------------------- @@ -945,11 +2172,14 @@ DatabaseContext::create(const std::string &databasePath, const std::vector<std::string> &auxiliaryDatabasePaths, PJ_CONTEXT *ctx) { auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>(); - dbCtx->getPrivate()->open(databasePath, ctx); + auto dbCtxPrivate = dbCtx->getPrivate(); + dbCtxPrivate->open(databasePath, ctx); if (!auxiliaryDatabasePaths.empty()) { - dbCtx->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths); + dbCtxPrivate->attachExtraDatabases(auxiliaryDatabasePaths); + dbCtxPrivate->auxiliaryDatabasePaths_ = auxiliaryDatabasePaths; } - dbCtx->getPrivate()->checkDatabaseLayout(); + dbCtxPrivate->checkDatabaseLayout(); + dbCtxPrivate->self_ = dbCtx.as_nullable(); return dbCtx; } @@ -999,6 +2229,259 @@ const char *DatabaseContext::getMetadata(const char *key) const { // --------------------------------------------------------------------------- +/** \brief Starts a session for getInsertStatementsFor() + * + * Starts a new session for one or several calls to getInsertStatementsFor(). + * An insertion session guarantees that the inserted objects will not create + * conflicting intermediate objects. + * + * The session must be stopped with stopInsertStatementsSession(). + * + * Only one session may be active at a time for a given database context. + * + * @throw FactoryException + * @since 8.1 + */ +void DatabaseContext::startInsertStatementsSession() { + if (d->memoryDbHandle_) { + throw FactoryException( + "startInsertStatementsSession() cannot be invoked until " + "stopInsertStatementsSession() is."); + } + + // Create a in-memory temporary sqlite3 database + std::ostringstream buffer; + buffer << "file:temp_db_for_insert_statements_"; + buffer << this; + buffer << ".db?mode=memory&cache=shared"; + d->memoryDbForInsertPath_ = buffer.str(); + sqlite3_open_v2( + d->memoryDbForInsertPath_.c_str(), &d->memoryDbHandle_, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr); + if (d->memoryDbHandle_ == nullptr) { + throw FactoryException("Cannot create in-memory database"); + } + + // Fill the structure of this database + const auto sqlStatements = getDatabaseStructure(); + for (const auto &sql : sqlStatements) { + if (sqlite3_exec(d->memoryDbHandle_, sql.c_str(), nullptr, nullptr, + nullptr) != SQLITE_OK) { + throw FactoryException("Cannot execute " + sql); + } + } + + // Attach this database to the current one(s) + auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_); + auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_); + d->attachExtraDatabases(auxiliaryDatabasePaths); +} + +// --------------------------------------------------------------------------- + +/** \brief Suggests a database code for the passed object. + * + * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble, + * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion. + * + * @param object Object for which to suggest a code. + * @param authName Authority name into which the object will be inserted. + * @param numericCode Whether the code should be numeric, or derived from the + * object name. + * @return the suggested code, that is guaranteed to not conflict with an + * existing one. + * + * @throw FactoryException + * @since 8.1 + */ +std::string +DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object, + const std::string &authName, + bool numericCode) { + const char *tableName = ""; + if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) { + tableName = "prime_meridian"; + } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) { + tableName = "ellipsoid"; + } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>( + object.get())) { + tableName = "geodetic_datum"; + } else if (dynamic_cast<const datum::VerticalReferenceFrame *>( + object.get())) { + tableName = "vertical_datum"; + } else if (const auto ensemble = + dynamic_cast<const datum::DatumEnsemble *>(object.get())) { + const auto &datums = ensemble->datums(); + if (!datums.empty() && + dynamic_cast<const datum::GeodeticReferenceFrame *>( + datums[0].get())) { + tableName = "geodetic_datum"; + } else { + tableName = "vertical_datum"; + } + } else if (const auto boundCRS = + dynamic_cast<const crs::BoundCRS *>(object.get())) { + return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode); + } else if (dynamic_cast<const crs::CRS *>(object.get())) { + tableName = "crs_view"; + } else if (dynamic_cast<const operation::Conversion *>(object.get())) { + tableName = "conversion"; + } else { + throw FactoryException("suggestsCodeFor(): unhandled type of object"); + } + + if (numericCode) { + std::string sql("SELECT MAX(code) FROM "); + sql += tableName; + sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' " + "AND upper(code) = lower(code)"; + const auto res = d->run(sql, {authName}); + if (res.empty()) { + return "1"; + } + return toString(atoi(res.front()[0].c_str()) + 1); + } + + std::string code; + code.reserve(object->nameStr().size()); + bool insertUnderscore = false; + for (const auto ch : toupper(object->nameStr())) { + if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) { + if (insertUnderscore && code.back() != '_') + code += '_'; + code += ch; + insertUnderscore = false; + } else { + insertUnderscore = true; + } + } + return d->findFreeCode(tableName, authName, code); +} + +// --------------------------------------------------------------------------- + +/** \brief Returns SQL statements needed to insert the passed object into the + * database. + * + * startInsertStatementsSession() must have been called previously. + * + * @param object The object to insert into the database. Currently only + * PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS, + * VerticalCRS, CompoundCRS or BoundCRS are supported. + * @param authName Authority name into which the object will be inserted. + * @param code Code with which the object will be inserted. + * @param numericCode Whether intermediate objects that can be created should + * use numeric codes (true), or may be alphanumeric (false) + * @throw FactoryException + * @since 8.1 + */ +std::vector<std::string> DatabaseContext::getInsertStatementsFor( + const common::IdentifiedObjectNNPtr &object, const std::string &authName, + const std::string &code, bool numericCode) { + if (d->memoryDbHandle_ == nullptr) { + throw FactoryException( + "startInsertStatementsSession() should be invoked first"); + } + + const auto self = NN_NO_CHECK(d->self_.lock()); + const auto allAuthFactory = AuthorityFactory::create(self, std::string()); + const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object); + if (crs) { + // Check if the object is already known under that code + const auto candidates = crs->identify(allAuthFactory); + for (const auto &candidate : candidates) { + if (candidate.second == 100) { + const auto &ids = candidate.first->identifiers(); + for (const auto &id : ids) { + if (*(id->codeSpace()) == authName && id->code() == code) { + return {}; + } + } + } + } + } + + if (const auto pm = + util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) { + return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code, + numericCode); + } + + else if (const auto ellipsoid = + util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) { + return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code, + numericCode); + } + + else if (const auto geodeticDatum = + util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>( + object)) { + return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName, + code, numericCode); + } + + else if (const auto geodCRS = + std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) { + return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code, + numericCode); + } + + else if (const auto projCRS = + std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) { + return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code, + numericCode); + } + + else if (const auto verticalDatum = + util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>( + object)) { + return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName, + code, numericCode); + } + + else if (const auto vertCRS = + std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) { + return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code, + numericCode); + } + + else if (const auto compoundCRS = + std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) { + return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName, + code, numericCode); + } + + else if (const auto boundCRS = + std::dynamic_pointer_cast<crs::BoundCRS>(crs)) { + return getInsertStatementsFor(boundCRS->baseCRS(), authName, code, + numericCode); + } + + else { + throw FactoryException( + "getInsertStatementsFor(): unhandled type of object"); + } +} + +// --------------------------------------------------------------------------- + +/** \brief Stops an insertion session started with + * startInsertStatementsSession() + * + * @since 8.1 + */ +void DatabaseContext::stopInsertStatementsSession() { + if (d->memoryDbHandle_) { + d->clearCaches(); + d->attachExtraDatabases(d->auxiliaryDatabasePaths_); + sqlite3_close(d->memoryDbHandle_); + d->memoryDbHandle_ = nullptr; + d->memoryDbForInsertPath_.clear(); + } +} + +// --------------------------------------------------------------------------- + //! @cond Doxygen_Suppress DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) { @@ -1518,7 +3001,12 @@ util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages( "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 = ? " + "object_code = ? AND " + // We voluntary exclude extent and scope with a specific code + "NOT (usage.extent_auth_name = 'PROJ' AND " + "usage.extent_code = 'EXTENT_UNKNOWN') AND " + "NOT (usage.scope_auth_name = 'PROJ' AND " + "usage.scope_code = 'SCOPE_UNKNOWN') " "ORDER BY score, usage.auth_name, usage.code"); auto res = run(sql, {table_name, authority(), code}); std::vector<ObjectDomainNNPtr> usages; @@ -2802,8 +4290,7 @@ AuthorityFactory::createConversion(const std::string &code) const { const size_t base_param_idx = idx; std::vector<operation::OperationParameterNNPtr> parameters; std::vector<operation::ParameterValueNNPtr> values; - constexpr int N_MAX_PARAMS = 7; - for (int i = 0; i < N_MAX_PARAMS; ++i) { + for (size_t 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; @@ -3523,8 +5010,7 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( "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) { + for (size_t i = 1; i <= N_MAX_PARAMS; ++i) { buffer << ", param" << i << "_auth_name"; buffer << ", param" << i << "_code"; buffer << ", param" << i << "_name"; @@ -3563,7 +5049,7 @@ operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation( const size_t base_param_idx = idx; std::vector<operation::OperationParameterNNPtr> parameters; std::vector<operation::ParameterValueNNPtr> values; - for (int i = 0; i < N_MAX_PARAMS; ++i) { + for (size_t 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; |
