Coverage Report

Created: 2026-04-10 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/iso19111/factory.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/coordinates.hpp"
36
#include "proj/coordinatesystem.hpp"
37
#include "proj/crs.hpp"
38
#include "proj/datum.hpp"
39
#include "proj/io.hpp"
40
#include "proj/metadata.hpp"
41
#include "proj/util.hpp"
42
43
#include "proj/internal/internal.hpp"
44
#include "proj/internal/io_internal.hpp"
45
#include "proj/internal/lru_cache.hpp"
46
#include "proj/internal/tracing.hpp"
47
48
#include "operation/coordinateoperation_internal.hpp"
49
#include "operation/parammappings.hpp"
50
51
#include "filemanager.hpp"
52
#include "sqlite3_utils.hpp"
53
54
#include <algorithm>
55
#include <cmath>
56
#include <cstdlib>
57
#include <cstring>
58
#include <functional>
59
#include <iomanip>
60
#include <limits>
61
#include <locale>
62
#include <map>
63
#include <memory>
64
#include <mutex>
65
#include <sstream> // std::ostringstream
66
#include <stdexcept>
67
#include <string>
68
69
#include "proj_constants.h"
70
71
// PROJ include order is sensitive
72
// clang-format off
73
#include "proj.h"
74
#include "proj_internal.h"
75
// clang-format on
76
77
#include <sqlite3.h>
78
79
#ifdef EMBED_RESOURCE_FILES
80
#include "embedded_resources.h"
81
#endif
82
83
// Custom SQLite VFS as our database is not supposed to be modified in
84
// parallel. This is slightly faster
85
#define ENABLE_CUSTOM_LOCKLESS_VFS
86
87
#if defined(_WIN32) && defined(PROJ_HAS_PTHREADS)
88
#undef PROJ_HAS_PTHREADS
89
#endif
90
91
/* SQLite3 might use seak()+read() or pread[64]() to read data */
92
/* The later allows the same SQLite handle to be safely used in forked */
93
/* children of a parent process, while the former doesn't. */
94
/* So we use pthread_atfork() to set a flag in forked children, to ask them */
95
/* to close and reopen their database handle. */
96
#if defined(PROJ_HAS_PTHREADS) && !defined(SQLITE_USE_PREAD)
97
#include <pthread.h>
98
#define REOPEN_SQLITE_DB_AFTER_FORK
99
#endif
100
101
using namespace NS_PROJ::internal;
102
using namespace NS_PROJ::common;
103
104
NS_PROJ_START
105
namespace io {
106
107
//! @cond Doxygen_Suppress
108
109
// CRS subtypes
110
8.49k
#define GEOG_2D "geographic 2D"
111
3.69k
#define GEOG_3D "geographic 3D"
112
3.53k
#define GEOCENTRIC "geocentric"
113
825
#define OTHER "other"
114
707
#define PROJECTED "projected"
115
393
#define DERIVED_PROJECTED "derived projected"
116
393
#define ENGINEERING "engineering"
117
809
#define VERTICAL "vertical"
118
381
#define COMPOUND "compound"
119
120
0
#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
121
0
#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
122
#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
123
124
// Coordinate system types
125
constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE;
126
constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE;
127
constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE;
128
constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE;
129
constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE;
130
131
// See data/sql/metadata.sql for the semantics of those constants
132
constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
133
// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
134
// must be incremented.
135
constexpr int DATABASE_LAYOUT_VERSION_MINOR = 7;
136
137
constexpr size_t N_MAX_PARAMS = 7;
138
139
#ifdef EMBED_RESOURCE_FILES
140
constexpr const char *EMBEDDED_PROJ_DB = "__embedded_proj_db__";
141
#endif
142
143
// ---------------------------------------------------------------------------
144
145
struct SQLValues {
146
    enum class Type { STRING, INT, DOUBLE };
147
148
    // cppcheck-suppress noExplicitConstructor
149
160k
    SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
150
151
    // cppcheck-suppress noExplicitConstructor
152
0
    SQLValues(int value) : type_(Type::INT), int_(value) {}
153
154
    // cppcheck-suppress noExplicitConstructor
155
14.3k
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
156
157
174k
    const Type &type() const { return type_; }
158
159
    // cppcheck-suppress functionStatic
160
160k
    const std::string &stringValue() const { return str_; }
161
162
    // cppcheck-suppress functionStatic
163
0
    int intValue() const { return int_; }
164
165
    // cppcheck-suppress functionStatic
166
14.3k
    double doubleValue() const { return double_; }
167
168
  private:
169
    Type type_;
170
    std::string str_{};
171
    int int_ = 0;
172
    double double_ = 0.0;
173
};
174
175
// ---------------------------------------------------------------------------
176
177
using SQLRow = std::vector<std::string>;
178
using SQLResultSet = std::list<SQLRow>;
179
using ListOfParams = std::list<SQLValues>;
180
181
// ---------------------------------------------------------------------------
182
183
0
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
184
0
    switch (sqlite3_value_type(val)) {
185
0
    case SQLITE_FLOAT:
186
0
        gotVal = true;
187
0
        return sqlite3_value_double(val);
188
189
0
    case SQLITE_INTEGER:
190
0
        gotVal = true;
191
0
        return static_cast<double>(sqlite3_value_int64(val));
192
193
0
    default:
194
0
        gotVal = false;
195
0
        return 0.0;
196
0
    }
197
0
}
198
199
// ---------------------------------------------------------------------------
200
201
static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
202
                                              int /* argc */,
203
0
                                              sqlite3_value **argv) {
204
0
    bool b0, b1, b2, b3;
205
0
    double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
206
0
    double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
207
0
    double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
208
0
    double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
209
0
    if (!b0 || !b1 || !b2 || !b3) {
210
0
        sqlite3_result_null(pContext);
211
0
        return;
212
0
    }
213
    // Deal with area crossing antimeridian
214
0
    if (east_lon < west_lon) {
215
0
        east_lon += 360.0;
216
0
    }
217
    // Integrate cos(lat) between south_lat and north_lat
218
0
    double pseudo_area = (east_lon - west_lon) *
219
0
                         (std::sin(common::Angle(north_lat).getSIValue()) -
220
0
                          std::sin(common::Angle(south_lat).getSIValue()));
221
0
    sqlite3_result_double(pContext, pseudo_area);
222
0
}
223
224
// ---------------------------------------------------------------------------
225
226
static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
227
0
                                        int /* argc */, sqlite3_value **argv) {
228
0
    bool b0, b1, b2, b3, b4, b5, b6, b7;
229
0
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
230
0
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
231
0
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
232
0
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
233
0
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
234
0
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
235
0
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
236
0
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
237
0
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
238
0
        sqlite3_result_null(pContext);
239
0
        return;
240
0
    }
241
0
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
242
0
                                                         east_lon1, north_lat1);
243
0
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
244
0
                                                         east_lon2, north_lat2);
245
0
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
246
0
}
247
248
// ---------------------------------------------------------------------------
249
250
class SQLiteHandle {
251
    std::string path_{};
252
    sqlite3 *sqlite_handle_ = nullptr;
253
    bool close_handle_ = true;
254
255
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
256
    bool is_valid_ = true;
257
#endif
258
259
    int nLayoutVersionMajor_ = 0;
260
    int nLayoutVersionMinor_ = 0;
261
262
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
263
    std::unique_ptr<SQLite3VFS> vfs_{};
264
#endif
265
266
    SQLiteHandle(const SQLiteHandle &) = delete;
267
    SQLiteHandle &operator=(const SQLiteHandle &) = delete;
268
269
    SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
270
1
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
271
1
        assert(sqlite_handle_);
272
1
    }
273
274
    // cppcheck-suppress functionStatic
275
    void initialize();
276
277
    SQLResultSet run(const std::string &sql,
278
                     const ListOfParams &parameters = ListOfParams(),
279
                     bool useMaxFloatPrecision = false);
280
281
  public:
282
    ~SQLiteHandle();
283
284
1.17k
    const std::string &path() const { return path_; }
285
286
1.78k
    sqlite3 *handle() { return sqlite_handle_; }
287
288
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
289
79.7k
    bool isValid() const { return is_valid_; }
290
291
0
    void invalidate() { is_valid_ = false; }
292
#endif
293
294
    static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
295
                                              const std::string &path);
296
297
    // might not be shared between thread depending how the handle was opened!
298
    static std::shared_ptr<SQLiteHandle>
299
    initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
300
                     int nLayoutVersionMajor, int nLayoutVersionMinor);
301
302
    static std::unique_ptr<SQLiteHandle>
303
    initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
304
305
    void checkDatabaseLayout(const std::string &mainDbPath,
306
                             const std::string &path,
307
                             const std::string &dbNamePrefix);
308
309
    SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
310
                     const ListOfParams &parameters = ListOfParams(),
311
                     bool useMaxFloatPrecision = false);
312
313
0
    inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
314
0
    inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
315
};
316
317
// ---------------------------------------------------------------------------
318
319
0
SQLiteHandle::~SQLiteHandle() {
320
0
    if (close_handle_) {
321
0
        sqlite3_close(sqlite_handle_);
322
0
    }
323
0
}
324
325
// ---------------------------------------------------------------------------
326
327
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
328
1
                                                 const std::string &pathIn) {
329
330
1
    std::string path(pathIn);
331
1
    const int sqlite3VersionNumber = sqlite3_libversion_number();
332
    // Minimum version for correct performance: 3.11
333
1
    if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
334
0
        pj_log(ctx, PJ_LOG_ERROR,
335
0
               "SQLite3 version is %s, whereas at least 3.11 should be used",
336
0
               sqlite3_libversion());
337
0
    }
338
339
1
    std::string vfsName;
340
1
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
341
1
    std::unique_ptr<SQLite3VFS> vfs;
342
1
#endif
343
344
1
#ifdef EMBED_RESOURCE_FILES
345
1
    if (path == EMBEDDED_PROJ_DB && ctx->custom_sqlite3_vfs_name.empty()) {
346
1
        unsigned int proj_db_size = 0;
347
1
        const unsigned char *proj_db = pj_get_embedded_proj_db(&proj_db_size);
348
349
1
        vfs = SQLite3VFS::createMem(proj_db, proj_db_size);
350
1
        if (vfs == nullptr) {
351
0
            throw FactoryException("Open of " + path + " failed");
352
0
        }
353
354
1
        std::ostringstream buffer;
355
1
        buffer << "file:/proj.db?immutable=1&ptr=";
356
1
        buffer << reinterpret_cast<uintptr_t>(proj_db);
357
1
        buffer << "&sz=";
358
1
        buffer << proj_db_size;
359
1
        buffer << "&max=";
360
1
        buffer << proj_db_size;
361
1
        buffer << "&vfs=";
362
1
        buffer << vfs->name();
363
1
        path = buffer.str();
364
1
    } else
365
0
#endif
366
367
0
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
368
0
        if (ctx->custom_sqlite3_vfs_name.empty()) {
369
0
        vfs = SQLite3VFS::create(false, true, true);
370
0
        if (vfs == nullptr) {
371
0
            throw FactoryException("Open of " + path + " failed");
372
0
        }
373
0
        vfsName = vfs->name();
374
0
    } else
375
0
#endif
376
0
    {
377
0
        vfsName = ctx->custom_sqlite3_vfs_name;
378
0
    }
379
1
    sqlite3 *sqlite_handle = nullptr;
380
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
381
1
    if (sqlite3_open_v2(
382
1
            path.c_str(), &sqlite_handle,
383
1
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
384
1
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
385
1
        !sqlite_handle) {
386
0
        if (sqlite_handle != nullptr) {
387
0
            sqlite3_close(sqlite_handle);
388
0
        }
389
0
        throw FactoryException("Open of " + path + " failed");
390
0
    }
391
1
    auto handle =
392
1
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
393
1
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
394
1
    handle->vfs_ = std::move(vfs);
395
1
#endif
396
1
    handle->initialize();
397
1
    handle->path_ = path;
398
1
    handle->checkDatabaseLayout(path, path, std::string());
399
1
    return handle;
400
1
}
401
402
// ---------------------------------------------------------------------------
403
404
std::shared_ptr<SQLiteHandle>
405
SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
406
                               int nLayoutVersionMajor,
407
0
                               int nLayoutVersionMinor) {
408
0
    auto handle = std::shared_ptr<SQLiteHandle>(
409
0
        new SQLiteHandle(sqlite_handle, close_handle));
410
0
    handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
411
0
    handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
412
0
    handle->initialize();
413
0
    return handle;
414
0
}
415
416
// ---------------------------------------------------------------------------
417
418
std::unique_ptr<SQLiteHandle>
419
SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
420
0
                                        bool close_handle) {
421
0
    auto handle = std::unique_ptr<SQLiteHandle>(
422
0
        new SQLiteHandle(sqlite_handle, close_handle));
423
0
    handle->initialize();
424
0
    return handle;
425
0
}
426
427
// ---------------------------------------------------------------------------
428
429
SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
430
                               const ListOfParams &parameters,
431
79.7k
                               bool useMaxFloatPrecision) {
432
79.7k
    int nBindField = 1;
433
174k
    for (const auto &param : parameters) {
434
174k
        const auto &paramType = param.type();
435
174k
        if (paramType == SQLValues::Type::STRING) {
436
160k
            const auto &strValue = param.stringValue();
437
160k
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
438
160k
                              static_cast<int>(strValue.size()),
439
160k
                              SQLITE_TRANSIENT);
440
160k
        } else if (paramType == SQLValues::Type::INT) {
441
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
442
14.3k
        } else {
443
14.3k
            assert(paramType == SQLValues::Type::DOUBLE);
444
14.3k
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
445
14.3k
        }
446
174k
        nBindField++;
447
174k
    }
448
449
#ifdef TRACE_DATABASE
450
    size_t nPos = 0;
451
    std::string sqlSubst(sql);
452
    for (const auto &param : parameters) {
453
        nPos = sqlSubst.find('?', nPos);
454
        assert(nPos != std::string::npos);
455
        std::string strValue;
456
        const auto paramType = param.type();
457
        if (paramType == SQLValues::Type::STRING) {
458
            strValue = '\'' + param.stringValue() + '\'';
459
        } else if (paramType == SQLValues::Type::INT) {
460
            strValue = toString(param.intValue());
461
        } else {
462
            strValue = toString(param.doubleValue());
463
        }
464
        sqlSubst =
465
            sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
466
        nPos += strValue.size();
467
    }
468
    logTrace(sqlSubst, "DATABASE");
469
#endif
470
471
79.7k
    SQLResultSet result;
472
79.7k
    const int column_count = sqlite3_column_count(stmt);
473
59.9M
    while (true) {
474
59.9M
        int ret = sqlite3_step(stmt);
475
59.9M
        if (ret == SQLITE_ROW) {
476
59.9M
            SQLRow row(column_count);
477
419M
            for (int i = 0; i < column_count; i++) {
478
359M
                if (useMaxFloatPrecision &&
479
492
                    sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
480
                    // sqlite3_column_text() does not use maximum precision
481
122
                    std::ostringstream buffer;
482
122
                    buffer.imbue(std::locale::classic());
483
122
                    buffer << std::setprecision(18);
484
122
                    buffer << sqlite3_column_double(stmt, i);
485
122
                    row[i] = buffer.str();
486
359M
                } else {
487
359M
                    const char *txt = reinterpret_cast<const char *>(
488
359M
                        sqlite3_column_text(stmt, i));
489
359M
                    if (txt) {
490
359M
                        row[i] = txt;
491
359M
                    }
492
359M
                }
493
359M
            }
494
59.9M
            result.emplace_back(std::move(row));
495
59.9M
        } else if (ret == SQLITE_DONE) {
496
79.7k
            break;
497
79.7k
        } else {
498
0
            throw FactoryException(std::string("SQLite error [ ")
499
0
                                       .append("code = ")
500
0
                                       .append(internal::toString(ret))
501
0
                                       .append(", msg = ")
502
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
503
0
                                       .append(" ] on ")
504
0
                                       .append(sql));
505
0
        }
506
59.9M
    }
507
79.7k
    return result;
508
79.7k
}
509
510
// ---------------------------------------------------------------------------
511
512
SQLResultSet SQLiteHandle::run(const std::string &sql,
513
                               const ListOfParams &parameters,
514
1
                               bool useMaxFloatPrecision) {
515
1
    sqlite3_stmt *stmt = nullptr;
516
1
    try {
517
1
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
518
1
                               static_cast<int>(sql.size()), &stmt,
519
1
                               nullptr) != SQLITE_OK) {
520
0
            throw FactoryException(std::string("SQLite error [ ")
521
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
522
0
                                       .append(" ] on ")
523
0
                                       .append(sql));
524
0
        }
525
1
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
526
1
        sqlite3_finalize(stmt);
527
1
        return ret;
528
1
    } catch (const std::exception &) {
529
0
        if (stmt)
530
0
            sqlite3_finalize(stmt);
531
0
        throw;
532
0
    }
533
1
}
534
535
// ---------------------------------------------------------------------------
536
537
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
538
                                       const std::string &path,
539
1
                                       const std::string &dbNamePrefix) {
540
1
    if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
541
0
                                     "sqlite_master WHERE name = 'metadata'")
542
0
                                     .empty()) {
543
        // Accept auxiliary databases without metadata table (sparse DBs)
544
0
        return;
545
0
    }
546
1
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
547
1
                   "metadata WHERE key IN "
548
1
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
549
1
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
550
1
    if (res.empty() && !dbNamePrefix.empty()) {
551
        // Accept auxiliary databases without layout metadata.
552
0
        return;
553
0
    }
554
1
    if (res.size() != 2) {
555
0
        throw FactoryException(
556
0
            path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
557
0
                   "DATABASE.LAYOUT.VERSION.MINOR "
558
0
                   "metadata. It comes from another PROJ installation.");
559
0
    }
560
1
    int major = 0;
561
1
    int minor = 0;
562
2
    for (const auto &row : res) {
563
2
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
564
1
            major = atoi(row[1].c_str());
565
1
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
566
1
            minor = atoi(row[1].c_str());
567
1
        }
568
2
    }
569
1
    if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
570
0
        throw FactoryException(
571
0
            path +
572
0
            " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
573
0
            " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
574
0
            " is expected. "
575
0
            "It comes from another PROJ installation.");
576
0
    }
577
578
1
    if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
579
0
        throw FactoryException(
580
0
            path +
581
0
            " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
582
0
            " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
583
0
            " is expected. "
584
0
            "It comes from another PROJ installation.");
585
0
    }
586
587
1
    if (dbNamePrefix.empty()) {
588
1
        nLayoutVersionMajor_ = major;
589
1
        nLayoutVersionMinor_ = minor;
590
1
    } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
591
0
        throw FactoryException(
592
0
            "Auxiliary database " + path +
593
0
            " contains a DATABASE.LAYOUT.VERSION =  " + toString(major) + '.' +
594
0
            toString(minor) +
595
0
            " which is different from the one from the main database " +
596
0
            mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
597
0
            toString(nLayoutVersionMinor_));
598
0
    }
599
1
}
600
601
// ---------------------------------------------------------------------------
602
603
#ifndef SQLITE_DETERMINISTIC
604
#define SQLITE_DETERMINISTIC 0
605
#endif
606
607
1
void SQLiteHandle::initialize() {
608
609
    // There is a bug in sqlite 3.38.0 with some complex queries.
610
    // Cf https://github.com/OSGeo/PROJ/issues/3077
611
    // Disabling Bloom-filter pull-down optimization as suggested in
612
    // https://sqlite.org/forum/forumpost/7d3a75438c
613
1
    const int sqlite3VersionNumber = sqlite3_libversion_number();
614
1
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
615
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
616
0
                             0x100000);
617
0
    }
618
619
1
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
620
1
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
621
1
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
622
1
                            nullptr);
623
624
1
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
625
1
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
626
1
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
627
1
}
628
629
// ---------------------------------------------------------------------------
630
631
class SQLiteHandleCache {
632
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
633
    bool firstTime_ = true;
634
#endif
635
636
    std::mutex sMutex_{};
637
638
    // Map dbname to SQLiteHandle
639
    lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
640
641
  public:
642
    static SQLiteHandleCache &get();
643
644
    std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
645
                                            PJ_CONTEXT *ctx);
646
647
    void clear();
648
649
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
650
    void invalidateHandles();
651
#endif
652
};
653
654
// ---------------------------------------------------------------------------
655
656
1.17k
SQLiteHandleCache &SQLiteHandleCache::get() {
657
    // Global cache
658
1.17k
    static SQLiteHandleCache gSQLiteHandleCache;
659
1.17k
    return gSQLiteHandleCache;
660
1.17k
}
661
662
// ---------------------------------------------------------------------------
663
664
0
void SQLiteHandleCache::clear() {
665
0
    std::lock_guard<std::mutex> lock(sMutex_);
666
0
    cache_.clear();
667
0
}
668
669
// ---------------------------------------------------------------------------
670
671
std::shared_ptr<SQLiteHandle>
672
1.17k
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
673
1.17k
    std::lock_guard<std::mutex> lock(sMutex_);
674
675
1.17k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
676
1.17k
    if (firstTime_) {
677
1
        firstTime_ = false;
678
1
        pthread_atfork(
679
1
            []() {
680
                // This mutex needs to be acquired by 'invalidateHandles()'.
681
                // The forking thread needs to own this mutex during the fork.
682
                // Otherwise there's an opporunity for another thread to own
683
                // the mutex during the fork, leaving the child process unable
684
                // to acquire the mutex in invalidateHandles().
685
0
                SQLiteHandleCache::get().sMutex_.lock();
686
0
            },
687
1
            []() { SQLiteHandleCache::get().sMutex_.unlock(); },
688
1
            []() {
689
0
                SQLiteHandleCache::get().sMutex_.unlock();
690
0
                SQLiteHandleCache::get().invalidateHandles();
691
0
            });
692
1
    }
693
1.17k
#endif
694
695
1.17k
    std::shared_ptr<SQLiteHandle> handle;
696
1.17k
    std::string key = path + ctx->custom_sqlite3_vfs_name;
697
1.17k
    if (!cache_.tryGet(key, handle)) {
698
1
        handle = SQLiteHandle::open(ctx, path);
699
1
        cache_.insert(key, handle);
700
1
    }
701
1.17k
    return handle;
702
1.17k
}
703
704
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
705
// ---------------------------------------------------------------------------
706
707
0
void SQLiteHandleCache::invalidateHandles() {
708
0
    std::lock_guard<std::mutex> lock(sMutex_);
709
0
    const auto lambda =
710
0
        [](const lru11::KeyValuePair<std::string, std::shared_ptr<SQLiteHandle>>
711
0
               &kvp) { kvp.value->invalidate(); };
712
0
    cache_.cwalk(lambda);
713
0
    cache_.clear();
714
0
}
715
#endif
716
717
// ---------------------------------------------------------------------------
718
719
struct DatabaseContext::Private {
720
    Private();
721
    ~Private();
722
723
    void open(const std::string &databasePath, PJ_CONTEXT *ctx);
724
    void setHandle(sqlite3 *sqlite_handle);
725
726
    const std::shared_ptr<SQLiteHandle> &handle();
727
728
1.17k
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
729
1.17k
    void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
730
731
    SQLResultSet run(const std::string &sql,
732
                     const ListOfParams &parameters = ListOfParams(),
733
                     bool useMaxFloatPrecision = false);
734
735
    std::vector<std::string> getDatabaseStructure();
736
737
    // cppcheck-suppress functionStatic
738
0
    const std::string &getPath() const { return databasePath_; }
739
740
    void attachExtraDatabases(
741
        const std::vector<std::string> &auxiliaryDatabasePaths);
742
743
    // Mechanism to detect recursion in calls from
744
    // AuthorityFactory::createXXX() -> createFromUserInput() ->
745
    // AuthorityFactory::createXXX()
746
    struct RecursionDetector {
747
        explicit RecursionDetector(const DatabaseContextNNPtr &context)
748
142
            : dbContext_(context) {
749
142
            if (dbContext_->getPrivate()->recLevel_ == 2) {
750
                // Throw exception before incrementing, since the destructor
751
                // will not be called
752
0
                throw FactoryException("Too many recursive calls");
753
0
            }
754
142
            ++dbContext_->getPrivate()->recLevel_;
755
142
        }
756
757
142
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
758
759
      private:
760
        DatabaseContextNNPtr dbContext_;
761
    };
762
763
1.83k
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
764
1.83k
        return mapCanonicalizeGRFName_;
765
1.83k
    }
766
767
    // cppcheck-suppress functionStatic
768
    common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
769
    // cppcheck-suppress functionStatic
770
    void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
771
772
    // cppcheck-suppress functionStatic
773
    crs::CRSPtr getCRSFromCache(const std::string &code);
774
    // cppcheck-suppress functionStatic
775
    void cache(const std::string &code, const crs::CRSNNPtr &crs);
776
777
    datum::GeodeticReferenceFramePtr
778
    // cppcheck-suppress functionStatic
779
    getGeodeticDatumFromCache(const std::string &code);
780
    // cppcheck-suppress functionStatic
781
    void cache(const std::string &code,
782
               const datum::GeodeticReferenceFrameNNPtr &datum);
783
784
    datum::DatumEnsemblePtr
785
    // cppcheck-suppress functionStatic
786
    getDatumEnsembleFromCache(const std::string &code);
787
    // cppcheck-suppress functionStatic
788
    void cache(const std::string &code,
789
               const datum::DatumEnsembleNNPtr &datumEnsemble);
790
791
    datum::EllipsoidPtr
792
    // cppcheck-suppress functionStatic
793
    getEllipsoidFromCache(const std::string &code);
794
    // cppcheck-suppress functionStatic
795
    void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
796
797
    datum::PrimeMeridianPtr
798
    // cppcheck-suppress functionStatic
799
    getPrimeMeridianFromCache(const std::string &code);
800
    // cppcheck-suppress functionStatic
801
    void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
802
803
    // cppcheck-suppress functionStatic
804
    cs::CoordinateSystemPtr
805
    getCoordinateSystemFromCache(const std::string &code);
806
    // cppcheck-suppress functionStatic
807
    void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
808
809
    // cppcheck-suppress functionStatic
810
    metadata::ExtentPtr getExtentFromCache(const std::string &code);
811
    // cppcheck-suppress functionStatic
812
    void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
813
814
    // cppcheck-suppress functionStatic
815
    bool getCRSToCRSCoordOpFromCache(
816
        const std::string &code,
817
        std::vector<operation::CoordinateOperationNNPtr> &list);
818
    // cppcheck-suppress functionStatic
819
    void cache(const std::string &code,
820
               const std::vector<operation::CoordinateOperationNNPtr> &list);
821
822
    struct GridInfoCache {
823
        std::string fullFilename{};
824
        std::string packageName{};
825
        std::string url{};
826
        bool found = false;
827
        bool directDownload = false;
828
        bool openLicense = false;
829
        bool gridAvailable = false;
830
    };
831
832
    // cppcheck-suppress functionStatic
833
    bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
834
    // cppcheck-suppress functionStatic
835
    void evictGridInfoFromCache(const std::string &code);
836
    // cppcheck-suppress functionStatic
837
    void cache(const std::string &code, const GridInfoCache &info);
838
839
    struct VersionedAuthName {
840
        std::string versionedAuthName{};
841
        std::string authName{};
842
        std::string version{};
843
        int priority = 0;
844
    };
845
    const std::vector<VersionedAuthName> &getCacheAuthNameWithVersion();
846
847
  private:
848
    friend class DatabaseContext;
849
850
    // This is a manual implementation of std::enable_shared_from_this<> that
851
    // avoids publicly deriving from it.
852
    std::weak_ptr<DatabaseContext> self_{};
853
854
    std::string databasePath_{};
855
    std::vector<std::string> auxiliaryDatabasePaths_{};
856
    std::shared_ptr<SQLiteHandle> sqlite_handle_{};
857
    unsigned int queryCounter_ = 0;
858
    std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
859
    PJ_CONTEXT *pjCtxt_ = nullptr;
860
    int recLevel_ = 0;
861
    bool detach_ = false;
862
    std::string lastMetadataValue_{};
863
    std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
864
865
    // Used by startInsertStatementsSession() and related functions
866
    std::string memoryDbForInsertPath_{};
867
    std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
868
869
    using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
870
871
    static constexpr size_t CACHE_SIZE = 128;
872
    LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
873
    LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
874
    LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
875
    LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
876
    LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE};
877
    LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
878
    LRUCacheOfObjects cacheCS_{CACHE_SIZE};
879
    LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
880
    lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
881
        cacheCRSToCrsCoordOp_{CACHE_SIZE};
882
    lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
883
884
    std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
885
886
    lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
887
        CACHE_SIZE};
888
    lru11::Cache<std::string, std::string> cacheNames_{CACHE_SIZE};
889
890
    std::vector<VersionedAuthName> cacheAuthNameWithVersion_{};
891
892
    static void insertIntoCache(LRUCacheOfObjects &cache,
893
                                const std::string &code,
894
                                const util::BaseObjectPtr &obj);
895
896
    static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
897
                             util::BaseObjectPtr &obj);
898
899
    void closeDB() noexcept;
900
901
    void clearCaches();
902
903
    std::string findFreeCode(const std::string &tableName,
904
                             const std::string &authName,
905
                             const std::string &codePrototype);
906
907
    void identify(const DatabaseContextNNPtr &dbContext,
908
                  const cs::CoordinateSystemNNPtr &obj, std::string &authName,
909
                  std::string &code);
910
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
911
                          const cs::CoordinateSystemNNPtr &obj,
912
                          const std::string &ownerType,
913
                          const std::string &ownerAuthName,
914
                          const std::string &ownerCode, std::string &authName,
915
                          std::string &code,
916
                          std::vector<std::string> &sqlStatements);
917
918
    void identify(const DatabaseContextNNPtr &dbContext,
919
                  const common::UnitOfMeasure &obj, std::string &authName,
920
                  std::string &code);
921
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
922
                          const common::UnitOfMeasure &unit,
923
                          const std::string &ownerAuthName,
924
                          std::string &authName, std::string &code,
925
                          std::vector<std::string> &sqlStatements);
926
927
    void appendSql(std::vector<std::string> &sqlStatements,
928
                   const std::string &sql);
929
930
    void
931
    identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj,
932
                           const std::string &tableName,
933
                           const std::string &authName, const std::string &code,
934
                           const std::vector<std::string> &allowedAuthorities,
935
                           std::vector<std::string> &sqlStatements);
936
937
    std::vector<std::string>
938
    getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm,
939
                           const std::string &authName, const std::string &code,
940
                           bool numericCode,
941
                           const std::vector<std::string> &allowedAuthorities);
942
943
    std::vector<std::string>
944
    getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid,
945
                           const std::string &authName, const std::string &code,
946
                           bool numericCode,
947
                           const std::vector<std::string> &allowedAuthorities);
948
949
    std::vector<std::string>
950
    getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum,
951
                           const std::string &authName, const std::string &code,
952
                           bool numericCode,
953
                           const std::vector<std::string> &allowedAuthorities);
954
955
    std::vector<std::string>
956
    getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble,
957
                           const std::string &authName, const std::string &code,
958
                           bool numericCode,
959
                           const std::vector<std::string> &allowedAuthorities);
960
961
    std::vector<std::string>
962
    getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs,
963
                           const std::string &authName, const std::string &code,
964
                           bool numericCode,
965
                           const std::vector<std::string> &allowedAuthorities);
966
967
    std::vector<std::string>
968
    getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs,
969
                           const std::string &authName, const std::string &code,
970
                           bool numericCode,
971
                           const std::vector<std::string> &allowedAuthorities);
972
973
    std::vector<std::string>
974
    getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum,
975
                           const std::string &authName, const std::string &code,
976
                           bool numericCode,
977
                           const std::vector<std::string> &allowedAuthorities);
978
979
    std::vector<std::string>
980
    getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs,
981
                           const std::string &authName, const std::string &code,
982
                           bool numericCode,
983
                           const std::vector<std::string> &allowedAuthorities);
984
985
    std::vector<std::string>
986
    getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs,
987
                           const std::string &authName, const std::string &code,
988
                           bool numericCode,
989
                           const std::vector<std::string> &allowedAuthorities);
990
991
    Private(const Private &) = delete;
992
    Private &operator=(const Private &) = delete;
993
};
994
995
// ---------------------------------------------------------------------------
996
997
1.17k
DatabaseContext::Private::Private() = default;
998
999
// ---------------------------------------------------------------------------
1000
1001
1.17k
DatabaseContext::Private::~Private() {
1002
1.17k
    assert(recLevel_ == 0);
1003
1004
1.17k
    closeDB();
1005
1.17k
}
1006
1007
// ---------------------------------------------------------------------------
1008
1009
1.17k
void DatabaseContext::Private::closeDB() noexcept {
1010
1011
1.17k
    if (detach_) {
1012
        // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
1013
        // a crash in TEST(factory, attachExtraDatabases_auxiliary)
1014
        // due to possible wrong caching of key info.
1015
        // The bug is specific to using a memory file with shared cache as an
1016
        // auxiliary DB.
1017
        // The fix was likely in 3.8.8
1018
        // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
1019
        // But just after 3.8.2,
1020
        // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
1021
        // also seemed to hide the issue.
1022
        // Detaching a database hides the issue, not sure if it is by chance...
1023
0
        try {
1024
0
            run("DETACH DATABASE db_0");
1025
0
        } catch (...) {
1026
0
        }
1027
0
        detach_ = false;
1028
0
    }
1029
1030
1.73k
    for (auto &pair : mapSqlToStatement_) {
1031
1.73k
        sqlite3_finalize(pair.second);
1032
1.73k
    }
1033
1.17k
    mapSqlToStatement_.clear();
1034
1035
1.17k
    sqlite_handle_.reset();
1036
1.17k
}
1037
1038
// ---------------------------------------------------------------------------
1039
1040
0
void DatabaseContext::Private::clearCaches() {
1041
1042
0
    cacheUOM_.clear();
1043
0
    cacheCRS_.clear();
1044
0
    cacheEllipsoid_.clear();
1045
0
    cacheGeodeticDatum_.clear();
1046
0
    cacheDatumEnsemble_.clear();
1047
0
    cachePrimeMeridian_.clear();
1048
0
    cacheCS_.clear();
1049
0
    cacheExtent_.clear();
1050
0
    cacheCRSToCrsCoordOp_.clear();
1051
0
    cacheGridInfo_.clear();
1052
0
    cacheAllowedAuthorities_.clear();
1053
0
    cacheAliasNames_.clear();
1054
0
    cacheNames_.clear();
1055
0
}
1056
1057
// ---------------------------------------------------------------------------
1058
1059
79.7k
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
1060
79.7k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
1061
79.7k
    if (sqlite_handle_ && !sqlite_handle_->isValid()) {
1062
0
        closeDB();
1063
0
        open(databasePath_, pjCtxt_);
1064
0
        if (!auxiliaryDatabasePaths_.empty()) {
1065
0
            attachExtraDatabases(auxiliaryDatabasePaths_);
1066
0
        }
1067
0
    }
1068
79.7k
#endif
1069
79.7k
    return sqlite_handle_;
1070
79.7k
}
1071
1072
// ---------------------------------------------------------------------------
1073
1074
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1075
                                               const std::string &code,
1076
7.05k
                                               const util::BaseObjectPtr &obj) {
1077
7.05k
    cache.insert(code, obj);
1078
7.05k
}
1079
1080
// ---------------------------------------------------------------------------
1081
1082
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1083
                                            const std::string &code,
1084
42.1k
                                            util::BaseObjectPtr &obj) {
1085
42.1k
    cache.tryGet(code, obj);
1086
42.1k
}
1087
1088
// ---------------------------------------------------------------------------
1089
1090
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1091
    const std::string &code,
1092
0
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1093
0
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1094
0
}
1095
1096
// ---------------------------------------------------------------------------
1097
1098
void DatabaseContext::Private::cache(
1099
    const std::string &code,
1100
0
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1101
0
    cacheCRSToCrsCoordOp_.insert(code, list);
1102
0
}
1103
1104
// ---------------------------------------------------------------------------
1105
1106
14.5k
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1107
14.5k
    util::BaseObjectPtr obj;
1108
14.5k
    getFromCache(cacheCRS_, code, obj);
1109
14.5k
    return std::static_pointer_cast<crs::CRS>(obj);
1110
14.5k
}
1111
1112
// ---------------------------------------------------------------------------
1113
1114
void DatabaseContext::Private::cache(const std::string &code,
1115
5.01k
                                     const crs::CRSNNPtr &crs) {
1116
5.01k
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1117
5.01k
}
1118
1119
// ---------------------------------------------------------------------------
1120
1121
common::UnitOfMeasurePtr
1122
11.7k
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1123
11.7k
    util::BaseObjectPtr obj;
1124
11.7k
    getFromCache(cacheUOM_, code, obj);
1125
11.7k
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1126
11.7k
}
1127
1128
// ---------------------------------------------------------------------------
1129
1130
void DatabaseContext::Private::cache(const std::string &code,
1131
123
                                     const common::UnitOfMeasureNNPtr &uom) {
1132
123
    insertIntoCache(cacheUOM_, code, uom.as_nullable());
1133
123
}
1134
1135
// ---------------------------------------------------------------------------
1136
1137
datum::GeodeticReferenceFramePtr
1138
3.82k
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1139
3.82k
    util::BaseObjectPtr obj;
1140
3.82k
    getFromCache(cacheGeodeticDatum_, code, obj);
1141
3.82k
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1142
3.82k
}
1143
1144
// ---------------------------------------------------------------------------
1145
1146
void DatabaseContext::Private::cache(
1147
1.56k
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1148
1.56k
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1149
1.56k
}
1150
1151
// ---------------------------------------------------------------------------
1152
1153
datum::DatumEnsemblePtr
1154
3.93k
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1155
3.93k
    util::BaseObjectPtr obj;
1156
3.93k
    getFromCache(cacheDatumEnsemble_, code, obj);
1157
3.93k
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1158
3.93k
}
1159
1160
// ---------------------------------------------------------------------------
1161
1162
void DatabaseContext::Private::cache(
1163
8
    const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) {
1164
8
    insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable());
1165
8
}
1166
1167
// ---------------------------------------------------------------------------
1168
1169
datum::EllipsoidPtr
1170
1.69k
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1171
1.69k
    util::BaseObjectPtr obj;
1172
1.69k
    getFromCache(cacheEllipsoid_, code, obj);
1173
1.69k
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1174
1.69k
}
1175
1176
// ---------------------------------------------------------------------------
1177
1178
void DatabaseContext::Private::cache(const std::string &code,
1179
142
                                     const datum::EllipsoidNNPtr &ellps) {
1180
142
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1181
142
}
1182
1183
// ---------------------------------------------------------------------------
1184
1185
datum::PrimeMeridianPtr
1186
1.56k
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1187
1.56k
    util::BaseObjectPtr obj;
1188
1.56k
    getFromCache(cachePrimeMeridian_, code, obj);
1189
1.56k
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1190
1.56k
}
1191
1192
// ---------------------------------------------------------------------------
1193
1194
void DatabaseContext::Private::cache(const std::string &code,
1195
53
                                     const datum::PrimeMeridianNNPtr &pm) {
1196
53
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1197
53
}
1198
1199
// ---------------------------------------------------------------------------
1200
1201
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1202
4.87k
    const std::string &code) {
1203
4.87k
    util::BaseObjectPtr obj;
1204
4.87k
    getFromCache(cacheCS_, code, obj);
1205
4.87k
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1206
4.87k
}
1207
1208
// ---------------------------------------------------------------------------
1209
1210
void DatabaseContext::Private::cache(const std::string &code,
1211
143
                                     const cs::CoordinateSystemNNPtr &cs) {
1212
143
    insertIntoCache(cacheCS_, code, cs.as_nullable());
1213
143
}
1214
1215
// ---------------------------------------------------------------------------
1216
1217
metadata::ExtentPtr
1218
0
DatabaseContext::Private::getExtentFromCache(const std::string &code) {
1219
0
    util::BaseObjectPtr obj;
1220
0
    getFromCache(cacheExtent_, code, obj);
1221
0
    return std::static_pointer_cast<metadata::Extent>(obj);
1222
0
}
1223
1224
// ---------------------------------------------------------------------------
1225
1226
void DatabaseContext::Private::cache(const std::string &code,
1227
0
                                     const metadata::ExtentNNPtr &extent) {
1228
0
    insertIntoCache(cacheExtent_, code, extent.as_nullable());
1229
0
}
1230
1231
// ---------------------------------------------------------------------------
1232
1233
bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
1234
0
                                                    GridInfoCache &info) {
1235
0
    return cacheGridInfo_.tryGet(code, info);
1236
0
}
1237
1238
// ---------------------------------------------------------------------------
1239
1240
0
void DatabaseContext::Private::evictGridInfoFromCache(const std::string &code) {
1241
0
    cacheGridInfo_.remove(code);
1242
0
}
1243
1244
// ---------------------------------------------------------------------------
1245
1246
void DatabaseContext::Private::cache(const std::string &code,
1247
0
                                     const GridInfoCache &info) {
1248
0
    cacheGridInfo_.insert(code, info);
1249
0
}
1250
1251
// ---------------------------------------------------------------------------
1252
1253
void DatabaseContext::Private::open(const std::string &databasePath,
1254
1.17k
                                    PJ_CONTEXT *ctx) {
1255
1.17k
    if (!ctx) {
1256
0
        ctx = pj_get_default_ctx();
1257
0
    }
1258
1259
1.17k
    setPjCtxt(ctx);
1260
1.17k
    std::string path(databasePath);
1261
1.17k
    if (path.empty()) {
1262
1.17k
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
1263
1.17k
        path.resize(2048);
1264
1.17k
        const bool found =
1265
1.17k
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
1266
1.17k
        path.resize(strlen(path.c_str()));
1267
1.17k
        if (!found)
1268
1.17k
#endif
1269
1.17k
        {
1270
1.17k
#ifdef EMBED_RESOURCE_FILES
1271
1.17k
            path = EMBEDDED_PROJ_DB;
1272
#else
1273
            throw FactoryException("Cannot find proj.db");
1274
#endif
1275
1.17k
        }
1276
1.17k
    }
1277
1278
1.17k
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1279
1280
1.17k
    databasePath_ = sqlite_handle_->path();
1281
1.17k
}
1282
1283
// ---------------------------------------------------------------------------
1284
1285
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1286
1287
0
    assert(sqlite_handle);
1288
0
    assert(!sqlite_handle_);
1289
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1290
0
}
1291
1292
// ---------------------------------------------------------------------------
1293
1294
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1295
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1296
0
                                           memoryDbForInsertPath_.empty()
1297
0
                                       ? ""
1298
0
                                       : "db_0.");
1299
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1300
0
                        "sqlite_master WHERE type = ");
1301
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1302
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1303
0
    std::vector<std::string> res;
1304
0
    for (const auto &objectType : objectTypes) {
1305
0
        const auto sqlRes = run(sqlBegin + objectType);
1306
0
        for (const auto &row : sqlRes) {
1307
0
            res.emplace_back(row[0]);
1308
0
        }
1309
0
    }
1310
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1311
0
        res.emplace_back(
1312
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1313
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1314
0
        res.emplace_back(
1315
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1316
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1317
0
    }
1318
0
    return res;
1319
0
}
1320
1321
// ---------------------------------------------------------------------------
1322
1323
void DatabaseContext::Private::attachExtraDatabases(
1324
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1325
1326
0
    auto l_handle = handle();
1327
0
    assert(l_handle);
1328
1329
0
    auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN "
1330
0
                      "('table', 'view') "
1331
0
                      "AND name NOT LIKE 'sqlite_stat%'");
1332
1333
0
    struct TableStructure {
1334
0
        std::string name{};
1335
0
        bool isTable = false;
1336
0
        std::string sql{};
1337
0
        std::vector<std::string> columns{};
1338
0
    };
1339
0
    std::vector<TableStructure> tablesStructure;
1340
0
    for (const auto &rowTable : tables) {
1341
0
        TableStructure tableStructure;
1342
0
        tableStructure.name = rowTable[0];
1343
0
        tableStructure.isTable = rowTable[1] == "table";
1344
0
        tableStructure.sql = rowTable[2];
1345
0
        auto tableInfo =
1346
0
            run("PRAGMA table_info(\"" +
1347
0
                replaceAll(tableStructure.name, "\"", "\"\"") + "\")");
1348
0
        for (const auto &rowCol : tableInfo) {
1349
0
            const auto &colName = rowCol[1];
1350
0
            tableStructure.columns.push_back(colName);
1351
0
        }
1352
0
        tablesStructure.push_back(std::move(tableStructure));
1353
0
    }
1354
1355
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1356
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1357
1358
0
    closeDB();
1359
0
    if (auxiliaryDatabasePaths.empty()) {
1360
0
        open(databasePath_, pjCtxt());
1361
0
        return;
1362
0
    }
1363
1364
0
    sqlite3 *sqlite_handle = nullptr;
1365
0
    sqlite3_open_v2(
1366
0
        ":memory:", &sqlite_handle,
1367
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1368
0
    if (!sqlite_handle) {
1369
0
        throw FactoryException("cannot create in memory database");
1370
0
    }
1371
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1372
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1373
0
    l_handle = sqlite_handle_;
1374
1375
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1376
0
    detach_ = true;
1377
0
    int count = 1;
1378
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1379
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1380
0
        std::string sql = "ATTACH DATABASE ? AS ";
1381
0
        sql += attachedDbName;
1382
0
        count++;
1383
0
        run(sql, {otherDbPath});
1384
1385
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1386
0
                                      attachedDbName + '.');
1387
0
    }
1388
1389
0
    for (const auto &tableStructure : tablesStructure) {
1390
0
        if (tableStructure.isTable) {
1391
0
            std::string sql("CREATE TEMP VIEW ");
1392
0
            sql += tableStructure.name;
1393
0
            sql += " AS ";
1394
0
            for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1395
0
                std::string selectFromAux("SELECT ");
1396
0
                bool firstCol = true;
1397
0
                for (const auto &colName : tableStructure.columns) {
1398
0
                    if (!firstCol) {
1399
0
                        selectFromAux += ", ";
1400
0
                    }
1401
0
                    firstCol = false;
1402
0
                    selectFromAux += colName;
1403
0
                }
1404
0
                selectFromAux += " FROM db_";
1405
0
                selectFromAux += toString(static_cast<int>(i));
1406
0
                selectFromAux += ".";
1407
0
                selectFromAux += tableStructure.name;
1408
1409
0
                try {
1410
                    // Check that the request will succeed. In case of 'sparse'
1411
                    // databases...
1412
0
                    run(selectFromAux + " LIMIT 0");
1413
1414
0
                    if (i > 0) {
1415
0
                        if (tableStructure.name == "conversion_method")
1416
0
                            sql += " UNION ";
1417
0
                        else
1418
0
                            sql += " UNION ALL ";
1419
0
                    }
1420
0
                    sql += selectFromAux;
1421
0
                } catch (const std::exception &) {
1422
0
                }
1423
0
            }
1424
0
            run(sql);
1425
0
        } else {
1426
0
            run(replaceAll(tableStructure.sql, "CREATE VIEW",
1427
0
                           "CREATE TEMP VIEW"));
1428
0
        }
1429
0
    }
1430
0
}
1431
1432
// ---------------------------------------------------------------------------
1433
1434
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1435
                                           const ListOfParams &parameters,
1436
79.7k
                                           bool useMaxFloatPrecision) {
1437
1438
79.7k
    auto l_handle = handle();
1439
79.7k
    assert(l_handle);
1440
1441
79.7k
    sqlite3_stmt *stmt = nullptr;
1442
79.7k
    auto iter = mapSqlToStatement_.find(sql);
1443
79.7k
    if (iter != mapSqlToStatement_.end()) {
1444
77.9k
        stmt = iter->second;
1445
77.9k
        sqlite3_reset(stmt);
1446
77.9k
    } else {
1447
1.78k
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1448
1.78k
                               static_cast<int>(sql.size()), &stmt,
1449
1.78k
                               nullptr) != SQLITE_OK) {
1450
0
            throw FactoryException(
1451
0
                std::string("SQLite error [ ")
1452
0
                    .append(sqlite3_errmsg(l_handle->handle()))
1453
0
                    .append(" ] on ")
1454
0
                    .append(sql));
1455
0
        }
1456
1.78k
        mapSqlToStatement_.insert(
1457
1.78k
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1458
1.78k
    }
1459
1460
79.7k
    ++queryCounter_;
1461
1462
79.7k
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1463
79.7k
}
1464
1465
// ---------------------------------------------------------------------------
1466
1467
0
static std::string formatStatement(const char *fmt, ...) {
1468
0
    std::string res;
1469
0
    va_list args;
1470
0
    va_start(args, fmt);
1471
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1472
0
        if (fmt[i] == '%') {
1473
0
            if (fmt[i + 1] == '%') {
1474
0
                res += '%';
1475
0
            } else if (fmt[i + 1] == 'q') {
1476
0
                const char *arg = va_arg(args, const char *);
1477
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1478
0
                    if (arg[j] == '\'')
1479
0
                        res += arg[j];
1480
0
                    res += arg[j];
1481
0
                }
1482
0
            } else if (fmt[i + 1] == 'Q') {
1483
0
                const char *arg = va_arg(args, const char *);
1484
0
                if (arg == nullptr)
1485
0
                    res += "NULL";
1486
0
                else {
1487
0
                    res += '\'';
1488
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1489
0
                        if (arg[j] == '\'')
1490
0
                            res += arg[j];
1491
0
                        res += arg[j];
1492
0
                    }
1493
0
                    res += '\'';
1494
0
                }
1495
0
            } else if (fmt[i + 1] == 's') {
1496
0
                const char *arg = va_arg(args, const char *);
1497
0
                res += arg;
1498
0
            } else if (fmt[i + 1] == 'f') {
1499
0
                const double arg = va_arg(args, double);
1500
0
                res += toString(arg);
1501
0
            } else if (fmt[i + 1] == 'd') {
1502
0
                const int arg = va_arg(args, int);
1503
0
                res += toString(arg);
1504
0
            } else {
1505
0
                va_end(args);
1506
0
                throw FactoryException(
1507
0
                    "Unsupported formatter in formatStatement()");
1508
0
            }
1509
0
            ++i;
1510
0
        } else {
1511
0
            res += fmt[i];
1512
0
        }
1513
0
    }
1514
0
    va_end(args);
1515
0
    return res;
1516
0
}
1517
1518
// ---------------------------------------------------------------------------
1519
1520
void DatabaseContext::Private::appendSql(
1521
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1522
0
    sqlStatements.emplace_back(sql);
1523
0
    char *errMsg = nullptr;
1524
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1525
0
                     &errMsg) != SQLITE_OK) {
1526
0
        std::string s("Cannot execute " + sql);
1527
0
        if (errMsg) {
1528
0
            s += " : ";
1529
0
            s += errMsg;
1530
0
        }
1531
0
        sqlite3_free(errMsg);
1532
0
        throw FactoryException(s);
1533
0
    }
1534
0
    sqlite3_free(errMsg);
1535
0
}
1536
1537
// ---------------------------------------------------------------------------
1538
1539
static void identifyFromNameOrCode(
1540
    const DatabaseContextNNPtr &dbContext,
1541
    const std::vector<std::string> &allowedAuthorities,
1542
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1543
    std::function<std::shared_ptr<util::IComparable>(
1544
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1545
        instantiateFunc,
1546
    AuthorityFactory::ObjectType objType, std::string &authName,
1547
0
    std::string &code) {
1548
1549
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1550
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1551
1552
0
    for (const auto &id : obj->identifiers()) {
1553
0
        try {
1554
0
            const auto &idAuthName = *(id->codeSpace());
1555
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1556
0
                          allowedAuthoritiesTmp.end(),
1557
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1558
0
                const auto factory =
1559
0
                    AuthorityFactory::create(dbContext, idAuthName);
1560
0
                if (instantiateFunc(factory, id->code())
1561
0
                        ->isEquivalentTo(
1562
0
                            obj.get(),
1563
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1564
0
                    authName = idAuthName;
1565
0
                    code = id->code();
1566
0
                    return;
1567
0
                }
1568
0
            }
1569
0
        } catch (const std::exception &) {
1570
0
        }
1571
0
    }
1572
1573
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1574
0
        const auto factory =
1575
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1576
0
        const auto candidates =
1577
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1578
0
        for (const auto &candidate : candidates) {
1579
0
            const auto &ids = candidate->identifiers();
1580
0
            if (!ids.empty() &&
1581
0
                candidate->isEquivalentTo(
1582
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1583
0
                const auto &id = ids.front();
1584
0
                authName = *(id->codeSpace());
1585
0
                code = id->code();
1586
0
                return;
1587
0
            }
1588
0
        }
1589
0
    }
1590
0
}
1591
1592
// ---------------------------------------------------------------------------
1593
1594
static void
1595
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1596
                       const std::vector<std::string> &allowedAuthorities,
1597
                       const std::string &authNameParent,
1598
                       const datum::DatumEnsembleNNPtr &obj,
1599
0
                       std::string &authName, std::string &code) {
1600
0
    const char *type = "geodetic_datum";
1601
0
    if (!obj->datums().empty() &&
1602
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1603
0
            obj->datums().front().get())) {
1604
0
        type = "vertical_datum";
1605
0
    }
1606
0
    const auto instantiateFunc =
1607
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1608
0
                const std::string &lCode) {
1609
0
            return util::nn_static_pointer_cast<util::IComparable>(
1610
0
                authFactory->createDatumEnsemble(lCode, type));
1611
0
        };
1612
0
    identifyFromNameOrCode(
1613
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1614
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1615
0
}
1616
1617
// ---------------------------------------------------------------------------
1618
1619
static void
1620
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1621
                       const std::vector<std::string> &allowedAuthorities,
1622
                       const std::string &authNameParent,
1623
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1624
0
                       std::string &authName, std::string &code) {
1625
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1626
0
                                    const std::string &lCode) {
1627
0
        return util::nn_static_pointer_cast<util::IComparable>(
1628
0
            authFactory->createGeodeticDatum(lCode));
1629
0
    };
1630
0
    identifyFromNameOrCode(
1631
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1632
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1633
0
}
1634
1635
// ---------------------------------------------------------------------------
1636
1637
static void
1638
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1639
                       const std::vector<std::string> &allowedAuthorities,
1640
                       const std::string &authNameParent,
1641
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1642
0
                       std::string &code) {
1643
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1644
0
                                    const std::string &lCode) {
1645
0
        return util::nn_static_pointer_cast<util::IComparable>(
1646
0
            authFactory->createEllipsoid(lCode));
1647
0
    };
1648
0
    identifyFromNameOrCode(
1649
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1650
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1651
0
}
1652
1653
// ---------------------------------------------------------------------------
1654
1655
static void
1656
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1657
                       const std::vector<std::string> &allowedAuthorities,
1658
                       const std::string &authNameParent,
1659
                       const datum::PrimeMeridianNNPtr &obj,
1660
0
                       std::string &authName, std::string &code) {
1661
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1662
0
                                    const std::string &lCode) {
1663
0
        return util::nn_static_pointer_cast<util::IComparable>(
1664
0
            authFactory->createPrimeMeridian(lCode));
1665
0
    };
1666
0
    identifyFromNameOrCode(
1667
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1668
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1669
0
}
1670
1671
// ---------------------------------------------------------------------------
1672
1673
static void
1674
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1675
                       const std::vector<std::string> &allowedAuthorities,
1676
                       const std::string &authNameParent,
1677
                       const datum::VerticalReferenceFrameNNPtr &obj,
1678
0
                       std::string &authName, std::string &code) {
1679
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1680
0
                                    const std::string &lCode) {
1681
0
        return util::nn_static_pointer_cast<util::IComparable>(
1682
0
            authFactory->createVerticalDatum(lCode));
1683
0
    };
1684
0
    identifyFromNameOrCode(
1685
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1686
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1687
0
}
1688
1689
// ---------------------------------------------------------------------------
1690
1691
static void
1692
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1693
                       const std::vector<std::string> &allowedAuthorities,
1694
                       const std::string &authNameParent,
1695
                       const datum::DatumNNPtr &obj, std::string &authName,
1696
0
                       std::string &code) {
1697
0
    if (const auto geodeticDatum =
1698
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1699
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1700
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1701
0
    } else if (const auto verticalDatum =
1702
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1703
0
                       obj)) {
1704
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1705
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1706
0
    } else {
1707
0
        throw FactoryException("Unhandled type of datum");
1708
0
    }
1709
0
}
1710
1711
// ---------------------------------------------------------------------------
1712
1713
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1714
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1715
0
        return CS_TYPE_ELLIPSOIDAL;
1716
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1717
0
        return CS_TYPE_CARTESIAN;
1718
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1719
0
        return CS_TYPE_VERTICAL;
1720
0
    }
1721
0
    return nullptr;
1722
0
}
1723
1724
// ---------------------------------------------------------------------------
1725
1726
std::string
1727
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1728
                                       const std::string &authName,
1729
0
                                       const std::string &codePrototype) {
1730
0
    std::string code(codePrototype);
1731
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1732
0
            {authName, code})
1733
0
            .empty()) {
1734
0
        return code;
1735
0
    }
1736
1737
0
    for (int counter = 2; counter < 10; counter++) {
1738
0
        code = codePrototype + '_' + toString(counter);
1739
0
        if (run("SELECT 1 FROM " + tableName +
1740
0
                    " WHERE auth_name = ? AND code = ?",
1741
0
                {authName, code})
1742
0
                .empty()) {
1743
0
            return code;
1744
0
        }
1745
0
    }
1746
1747
    // shouldn't happen hopefully...
1748
0
    throw FactoryException("Cannot insert " + tableName +
1749
0
                           ": too many similar codes");
1750
0
}
1751
1752
// ---------------------------------------------------------------------------
1753
1754
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1755
0
    switch (unit.type()) {
1756
0
    case common::UnitOfMeasure::Type::LINEAR:
1757
0
        return "length";
1758
1759
0
    case common::UnitOfMeasure::Type::ANGULAR:
1760
0
        return "angle";
1761
1762
0
    case common::UnitOfMeasure::Type::SCALE:
1763
0
        return "scale";
1764
1765
0
    case common::UnitOfMeasure::Type::TIME:
1766
0
        return "time";
1767
1768
0
    default:
1769
0
        break;
1770
0
    }
1771
0
    return nullptr;
1772
0
}
1773
1774
// ---------------------------------------------------------------------------
1775
1776
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1777
                                        const common::UnitOfMeasure &obj,
1778
                                        std::string &authName,
1779
0
                                        std::string &code) {
1780
    // Identify quickly a few well-known units
1781
0
    const double convFactor = obj.conversionToSI();
1782
0
    switch (obj.type()) {
1783
0
    case common::UnitOfMeasure::Type::LINEAR: {
1784
0
        if (convFactor == 1.0) {
1785
0
            authName = metadata::Identifier::EPSG;
1786
0
            code = "9001";
1787
0
            return;
1788
0
        }
1789
0
        break;
1790
0
    }
1791
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1792
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1793
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1794
0
            1e-10 * CONV_FACTOR_DEGREE) {
1795
0
            authName = metadata::Identifier::EPSG;
1796
0
            code = "9102";
1797
0
            return;
1798
0
        }
1799
0
        break;
1800
0
    }
1801
0
    case common::UnitOfMeasure::Type::SCALE: {
1802
0
        if (convFactor == 1.0) {
1803
0
            authName = metadata::Identifier::EPSG;
1804
0
            code = "9201";
1805
0
            return;
1806
0
        }
1807
0
        break;
1808
0
    }
1809
0
    default:
1810
0
        break;
1811
0
    }
1812
1813
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1814
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1815
0
    ListOfParams params{convFactor};
1816
0
    const char *type = getUnitDatabaseType(obj);
1817
0
    if (type) {
1818
0
        sql += " AND type = ?";
1819
0
        params.emplace_back(std::string(type));
1820
0
    }
1821
0
    sql += " ORDER BY auth_name, code";
1822
0
    const auto res = run(sql, params);
1823
0
    for (const auto &row : res) {
1824
0
        const auto &rowAuthName = row[0];
1825
0
        const auto &rowCode = row[1];
1826
0
        const auto tmpAuthFactory =
1827
0
            AuthorityFactory::create(dbContext, rowAuthName);
1828
0
        try {
1829
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1830
0
            authName = rowAuthName;
1831
0
            code = rowCode;
1832
0
            return;
1833
0
        } catch (const std::exception &) {
1834
0
        }
1835
0
    }
1836
0
}
1837
1838
// ---------------------------------------------------------------------------
1839
1840
void DatabaseContext::Private::identifyOrInsert(
1841
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1842
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1843
0
    std::vector<std::string> &sqlStatements) {
1844
0
    authName = unit.codeSpace();
1845
0
    code = unit.code();
1846
0
    if (authName.empty()) {
1847
0
        identify(dbContext, unit, authName, code);
1848
0
    }
1849
0
    if (!authName.empty()) {
1850
0
        return;
1851
0
    }
1852
0
    const char *type = getUnitDatabaseType(unit);
1853
0
    if (type == nullptr) {
1854
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1855
0
    }
1856
1857
    // Insert new record
1858
0
    authName = ownerAuthName;
1859
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1860
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1861
1862
0
    const auto sql = formatStatement(
1863
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1864
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1865
0
        unit.conversionToSI());
1866
0
    appendSql(sqlStatements, sql);
1867
0
}
1868
1869
// ---------------------------------------------------------------------------
1870
1871
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1872
                                        const cs::CoordinateSystemNNPtr &obj,
1873
                                        std::string &authName,
1874
0
                                        std::string &code) {
1875
1876
0
    const auto &axisList = obj->axisList();
1877
0
    if (axisList.size() == 1U &&
1878
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1879
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1880
0
        (axisList[0]->nameStr() == "Up" ||
1881
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1882
        // preferred coordinate system for gravity-related height
1883
0
        authName = metadata::Identifier::EPSG;
1884
0
        code = "6499";
1885
0
        return;
1886
0
    }
1887
1888
0
    std::string sql(
1889
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1890
0
    ListOfParams params{static_cast<int>(axisList.size())};
1891
0
    const char *type = getCSDatabaseType(obj);
1892
0
    if (type) {
1893
0
        sql += " AND type = ?";
1894
0
        params.emplace_back(std::string(type));
1895
0
    }
1896
0
    sql += " ORDER BY auth_name, code";
1897
0
    const auto res = run(sql, params);
1898
0
    for (const auto &row : res) {
1899
0
        const auto &rowAuthName = row[0];
1900
0
        const auto &rowCode = row[1];
1901
0
        const auto tmpAuthFactory =
1902
0
            AuthorityFactory::create(dbContext, rowAuthName);
1903
0
        try {
1904
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1905
0
            if (cs->_isEquivalentTo(obj.get(),
1906
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1907
0
                authName = rowAuthName;
1908
0
                code = rowCode;
1909
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1910
                    // preferred coordinate system for cartesian
1911
                    // Easting, Northing
1912
0
                    return;
1913
0
                }
1914
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1915
                    // preferred coordinate system for geographic lat, long
1916
0
                    return;
1917
0
                }
1918
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1919
                    // preferred coordinate system for geographic lat, long, h
1920
0
                    return;
1921
0
                }
1922
0
            }
1923
0
        } catch (const std::exception &) {
1924
0
        }
1925
0
    }
1926
0
}
1927
1928
// ---------------------------------------------------------------------------
1929
1930
void DatabaseContext::Private::identifyOrInsert(
1931
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1932
    const std::string &ownerType, const std::string &ownerAuthName,
1933
    const std::string &ownerCode, std::string &authName, std::string &code,
1934
0
    std::vector<std::string> &sqlStatements) {
1935
1936
0
    identify(dbContext, obj, authName, code);
1937
0
    if (!authName.empty()) {
1938
0
        return;
1939
0
    }
1940
1941
0
    const char *type = getCSDatabaseType(obj);
1942
0
    if (type == nullptr) {
1943
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1944
0
    }
1945
1946
    // Insert new record in coordinate_system
1947
0
    authName = ownerAuthName;
1948
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1949
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1950
1951
0
    const auto &axisList = obj->axisList();
1952
0
    {
1953
0
        const auto sql = formatStatement(
1954
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1955
0
            authName.c_str(), code.c_str(), type,
1956
0
            static_cast<int>(axisList.size()));
1957
0
        appendSql(sqlStatements, sql);
1958
0
    }
1959
1960
    // Insert new records for the axis
1961
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1962
0
        const auto &axis = axisList[i];
1963
0
        std::string uomAuthName;
1964
0
        std::string uomCode;
1965
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1966
0
                         uomCode, sqlStatements);
1967
0
        const auto sql = formatStatement(
1968
0
            "INSERT INTO axis VALUES("
1969
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1970
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1971
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1972
0
            axis->direction().toString().c_str(), authName.c_str(),
1973
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1974
0
        appendSql(sqlStatements, sql);
1975
0
    }
1976
0
}
1977
1978
// ---------------------------------------------------------------------------
1979
1980
static void
1981
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1982
                          const std::string &authName, std::string &sql,
1983
0
                          ListOfParams &params) {
1984
0
    sql += "auth_name IN (?";
1985
0
    params.emplace_back(authName);
1986
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1987
0
        sql += ",?";
1988
0
        params.emplace_back(allowedAuthority);
1989
0
    }
1990
0
    sql += ')';
1991
0
}
1992
1993
// ---------------------------------------------------------------------------
1994
1995
void DatabaseContext::Private::identifyOrInsertUsages(
1996
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1997
    const std::string &authName, const std::string &code,
1998
    const std::vector<std::string> &allowedAuthorities,
1999
0
    std::vector<std::string> &sqlStatements) {
2000
2001
0
    std::string usageCode("USAGE_");
2002
0
    const std::string upperTableName(toupper(tableName));
2003
0
    if (!starts_with(code, upperTableName)) {
2004
0
        usageCode += upperTableName;
2005
0
        usageCode += '_';
2006
0
    }
2007
0
    usageCode += code;
2008
2009
0
    const auto &domains = obj->domains();
2010
0
    if (domains.empty()) {
2011
0
        const auto sql =
2012
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2013
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
2014
0
                            authName.c_str(), usageCode.c_str(),
2015
0
                            tableName.c_str(), authName.c_str(), code.c_str());
2016
0
        appendSql(sqlStatements, sql);
2017
0
        return;
2018
0
    }
2019
2020
0
    int usageCounter = 1;
2021
0
    for (const auto &domain : domains) {
2022
0
        std::string scopeAuthName;
2023
0
        std::string scopeCode;
2024
0
        const auto &scope = domain->scope();
2025
0
        if (scope.has_value()) {
2026
0
            std::string sql =
2027
0
                "SELECT auth_name, code, "
2028
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2029
0
                "AS order_idx "
2030
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
2031
0
            ListOfParams params{*scope};
2032
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2033
0
                                      params);
2034
0
            sql += " ORDER BY order_idx, auth_name, code";
2035
0
            const auto rows = run(sql, params);
2036
0
            if (!rows.empty()) {
2037
0
                const auto &row = rows.front();
2038
0
                scopeAuthName = row[0];
2039
0
                scopeCode = row[1];
2040
0
            } else {
2041
0
                scopeAuthName = authName;
2042
0
                scopeCode = "SCOPE_";
2043
0
                scopeCode += tableName;
2044
0
                scopeCode += '_';
2045
0
                scopeCode += code;
2046
0
                const auto sqlToInsert = formatStatement(
2047
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
2048
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
2049
0
                appendSql(sqlStatements, sqlToInsert);
2050
0
            }
2051
0
        } else {
2052
0
            scopeAuthName = "PROJ";
2053
0
            scopeCode = "SCOPE_UNKNOWN";
2054
0
        }
2055
2056
0
        std::string extentAuthName("PROJ");
2057
0
        std::string extentCode("EXTENT_UNKNOWN");
2058
0
        const auto &extent = domain->domainOfValidity();
2059
0
        if (extent) {
2060
0
            const auto &geogElts = extent->geographicElements();
2061
0
            if (!geogElts.empty()) {
2062
0
                const auto bbox =
2063
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
2064
0
                        geogElts.front().get());
2065
0
                if (bbox) {
2066
0
                    std::string sql =
2067
0
                        "SELECT auth_name, code, "
2068
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2069
0
                        "AS order_idx "
2070
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
2071
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
2072
0
                        "AND ";
2073
0
                    ListOfParams params{
2074
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
2075
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
2076
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2077
0
                                              params);
2078
0
                    sql += " ORDER BY order_idx, auth_name, code";
2079
0
                    const auto rows = run(sql, params);
2080
0
                    if (!rows.empty()) {
2081
0
                        const auto &row = rows.front();
2082
0
                        extentAuthName = row[0];
2083
0
                        extentCode = row[1];
2084
0
                    } else {
2085
0
                        extentAuthName = authName;
2086
0
                        extentCode = "EXTENT_";
2087
0
                        extentCode += tableName;
2088
0
                        extentCode += '_';
2089
0
                        extentCode += code;
2090
0
                        std::string description(*(extent->description()));
2091
0
                        if (description.empty()) {
2092
0
                            description = "unknown";
2093
0
                        }
2094
0
                        const auto sqlToInsert = formatStatement(
2095
0
                            "INSERT INTO extent "
2096
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
2097
0
                            extentAuthName.c_str(), extentCode.c_str(),
2098
0
                            description.c_str(), description.c_str(),
2099
0
                            bbox->southBoundLatitude(),
2100
0
                            bbox->northBoundLatitude(),
2101
0
                            bbox->westBoundLongitude(),
2102
0
                            bbox->eastBoundLongitude());
2103
0
                        appendSql(sqlStatements, sqlToInsert);
2104
0
                    }
2105
0
                }
2106
0
            }
2107
0
        }
2108
2109
0
        if (domains.size() > 1) {
2110
0
            usageCode += '_';
2111
0
            usageCode += toString(usageCounter);
2112
0
        }
2113
0
        const auto sql = formatStatement(
2114
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2115
0
            "'%q','%q','%q','%q');",
2116
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2117
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2118
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2119
0
        appendSql(sqlStatements, sql);
2120
2121
0
        usageCounter++;
2122
0
    }
2123
0
}
2124
2125
// ---------------------------------------------------------------------------
2126
2127
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2128
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2129
    const std::string &code, bool /*numericCode*/,
2130
0
    const std::vector<std::string> &allowedAuthorities) {
2131
2132
0
    const auto self = NN_NO_CHECK(self_.lock());
2133
2134
    // Check if the object is already known under that code
2135
0
    std::string pmAuthName;
2136
0
    std::string pmCode;
2137
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2138
0
                           pmCode);
2139
0
    if (pmAuthName == authName && pmCode == code) {
2140
0
        return {};
2141
0
    }
2142
2143
0
    std::vector<std::string> sqlStatements;
2144
2145
    // Insert new record in prime_meridian table
2146
0
    std::string uomAuthName;
2147
0
    std::string uomCode;
2148
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2149
0
                     uomCode, sqlStatements);
2150
2151
0
    const auto sql = formatStatement(
2152
0
        "INSERT INTO prime_meridian VALUES("
2153
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2154
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2155
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2156
0
    appendSql(sqlStatements, sql);
2157
2158
0
    return sqlStatements;
2159
0
}
2160
2161
// ---------------------------------------------------------------------------
2162
2163
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2164
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2165
    const std::string &code, bool /*numericCode*/,
2166
0
    const std::vector<std::string> &allowedAuthorities) {
2167
2168
0
    const auto self = NN_NO_CHECK(self_.lock());
2169
2170
    // Check if the object is already known under that code
2171
0
    std::string ellipsoidAuthName;
2172
0
    std::string ellipsoidCode;
2173
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2174
0
                           ellipsoidAuthName, ellipsoidCode);
2175
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2176
0
        return {};
2177
0
    }
2178
2179
0
    std::vector<std::string> sqlStatements;
2180
2181
    // Find or insert celestial body
2182
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2183
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2184
0
    constexpr double tolerance = 0.005;
2185
0
    std::string bodyAuthName;
2186
0
    std::string bodyCode;
2187
0
    auto res = run("SELECT auth_name, code, "
2188
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2189
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2190
0
                   {semiMajorAxisMetre, tolerance});
2191
0
    if (!res.empty()) {
2192
0
        const auto &row = res.front();
2193
0
        bodyAuthName = row[0];
2194
0
        bodyCode = row[1];
2195
0
    } else {
2196
0
        bodyAuthName = authName;
2197
0
        bodyCode = "BODY_" + code;
2198
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2199
0
        const auto sql = formatStatement(
2200
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2201
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2202
0
            semiMajorAxisMetre);
2203
0
        appendSql(sqlStatements, sql);
2204
0
    }
2205
2206
    // Insert new record in ellipsoid table
2207
0
    std::string uomAuthName;
2208
0
    std::string uomCode;
2209
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2210
0
                     sqlStatements);
2211
0
    std::string invFlattening("NULL");
2212
0
    std::string semiMinorAxis("NULL");
2213
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2214
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2215
0
    } else {
2216
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2217
0
    }
2218
2219
0
    const auto sql = formatStatement(
2220
0
        "INSERT INTO ellipsoid VALUES("
2221
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2222
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2223
0
        "", // description
2224
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2225
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2226
0
        semiMinorAxis.c_str());
2227
0
    appendSql(sqlStatements, sql);
2228
2229
0
    return sqlStatements;
2230
0
}
2231
2232
// ---------------------------------------------------------------------------
2233
2234
0
static std::string anchorEpochToStr(double val) {
2235
0
    constexpr int BUF_SIZE = 16;
2236
0
    char szBuffer[BUF_SIZE];
2237
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2238
0
    return szBuffer;
2239
0
}
2240
2241
// ---------------------------------------------------------------------------
2242
2243
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2244
    const datum::GeodeticReferenceFrameNNPtr &datum,
2245
    const std::string &authName, const std::string &code, bool numericCode,
2246
0
    const std::vector<std::string> &allowedAuthorities) {
2247
2248
0
    const auto self = NN_NO_CHECK(self_.lock());
2249
2250
    // Check if the object is already known under that code
2251
0
    std::string datumAuthName;
2252
0
    std::string datumCode;
2253
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2254
0
                           datumAuthName, datumCode);
2255
0
    if (datumAuthName == authName && datumCode == code) {
2256
0
        return {};
2257
0
    }
2258
2259
0
    std::vector<std::string> sqlStatements;
2260
2261
    // Find or insert ellipsoid
2262
0
    std::string ellipsoidAuthName;
2263
0
    std::string ellipsoidCode;
2264
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2265
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2266
0
                           ellipsoidAuthName, ellipsoidCode);
2267
0
    if (ellipsoidAuthName.empty()) {
2268
0
        ellipsoidAuthName = authName;
2269
0
        if (numericCode) {
2270
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2271
0
                                                  ellipsoidAuthName, true);
2272
0
        } else {
2273
0
            ellipsoidCode = "ELLPS_" + code;
2274
0
        }
2275
0
        sqlStatements = self->getInsertStatementsFor(
2276
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2277
0
            allowedAuthorities);
2278
0
    }
2279
2280
    // Find or insert prime meridian
2281
0
    std::string pmAuthName;
2282
0
    std::string pmCode;
2283
0
    const auto &pmOfDatum = datum->primeMeridian();
2284
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2285
0
                           pmAuthName, pmCode);
2286
0
    if (pmAuthName.empty()) {
2287
0
        pmAuthName = authName;
2288
0
        if (numericCode) {
2289
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2290
0
        } else {
2291
0
            pmCode = "PM_" + code;
2292
0
        }
2293
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2294
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2295
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2296
0
                             sqlStatementsTmp.end());
2297
0
    }
2298
2299
    // Insert new record in geodetic_datum table
2300
0
    std::string publicationDate("NULL");
2301
0
    if (datum->publicationDate().has_value()) {
2302
0
        publicationDate = '\'';
2303
0
        publicationDate +=
2304
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2305
0
        publicationDate += '\'';
2306
0
    }
2307
0
    std::string frameReferenceEpoch("NULL");
2308
0
    const auto dynamicDatum =
2309
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2310
0
    if (dynamicDatum) {
2311
0
        frameReferenceEpoch =
2312
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2313
0
    }
2314
0
    const std::string anchor(*(datum->anchorDefinition()));
2315
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2316
0
    const auto sql = formatStatement(
2317
0
        "INSERT INTO geodetic_datum VALUES("
2318
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2319
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2320
0
        "", // description
2321
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2322
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
2323
0
        anchor.empty() ? nullptr : anchor.c_str(),
2324
0
        anchorEpoch.has_value()
2325
0
            ? anchorEpochToStr(
2326
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2327
0
                  .c_str()
2328
0
            : "NULL");
2329
0
    appendSql(sqlStatements, sql);
2330
2331
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2332
0
                           allowedAuthorities, sqlStatements);
2333
2334
0
    return sqlStatements;
2335
0
}
2336
2337
// ---------------------------------------------------------------------------
2338
2339
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2340
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2341
    const std::string &code, bool numericCode,
2342
0
    const std::vector<std::string> &allowedAuthorities) {
2343
0
    const auto self = NN_NO_CHECK(self_.lock());
2344
2345
    // Check if the object is already known under that code
2346
0
    std::string datumAuthName;
2347
0
    std::string datumCode;
2348
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2349
0
                           datumAuthName, datumCode);
2350
0
    if (datumAuthName == authName && datumCode == code) {
2351
0
        return {};
2352
0
    }
2353
2354
0
    std::vector<std::string> sqlStatements;
2355
2356
0
    const auto &members = ensemble->datums();
2357
0
    assert(!members.empty());
2358
2359
0
    int counter = 1;
2360
0
    std::vector<std::pair<std::string, std::string>> membersId;
2361
0
    for (const auto &member : members) {
2362
0
        std::string memberAuthName;
2363
0
        std::string memberCode;
2364
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2365
0
                               memberAuthName, memberCode);
2366
0
        if (memberAuthName.empty()) {
2367
0
            memberAuthName = authName;
2368
0
            if (numericCode) {
2369
0
                memberCode =
2370
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2371
0
            } else {
2372
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2373
0
            }
2374
0
            const auto sqlStatementsTmp =
2375
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2376
0
                                             numericCode, allowedAuthorities);
2377
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2378
0
                                 sqlStatementsTmp.end());
2379
0
        }
2380
2381
0
        membersId.emplace_back(
2382
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2383
2384
0
        ++counter;
2385
0
    }
2386
2387
0
    const bool isGeodetic =
2388
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2389
0
            members.front()) != nullptr;
2390
2391
    // Insert new record in geodetic_datum/vertical_datum table
2392
0
    const double accuracy =
2393
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2394
0
    if (isGeodetic) {
2395
0
        const auto firstDatum =
2396
0
            AuthorityFactory::create(self, membersId.front().first)
2397
0
                ->createGeodeticDatum(membersId.front().second);
2398
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2399
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2400
0
        assert(!ellipsoidIds.empty());
2401
0
        const std::string &ellipsoidAuthName =
2402
0
            *(ellipsoidIds.front()->codeSpace());
2403
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2404
0
        const auto &pm = firstDatum->primeMeridian();
2405
0
        const auto &pmIds = pm->identifiers();
2406
0
        assert(!pmIds.empty());
2407
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2408
0
        const std::string &pmCode = pmIds.front()->code();
2409
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2410
0
        const util::optional<common::Measure> &anchorEpoch =
2411
0
            firstDatum->anchorEpoch();
2412
0
        const auto sql = formatStatement(
2413
0
            "INSERT INTO geodetic_datum VALUES("
2414
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2415
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2416
0
            "", // description
2417
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2418
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2419
0
            anchor.empty() ? nullptr : anchor.c_str(),
2420
0
            anchorEpoch.has_value()
2421
0
                ? anchorEpochToStr(
2422
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2423
0
                      .c_str()
2424
0
                : "NULL");
2425
0
        appendSql(sqlStatements, sql);
2426
0
    } else {
2427
0
        const auto firstDatum =
2428
0
            AuthorityFactory::create(self, membersId.front().first)
2429
0
                ->createVerticalDatum(membersId.front().second);
2430
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2431
0
        const util::optional<common::Measure> &anchorEpoch =
2432
0
            firstDatum->anchorEpoch();
2433
0
        const auto sql = formatStatement(
2434
0
            "INSERT INTO vertical_datum VALUES("
2435
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2436
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2437
0
            "", // description
2438
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2439
0
            anchorEpoch.has_value()
2440
0
                ? anchorEpochToStr(
2441
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2442
0
                      .c_str()
2443
0
                : "NULL");
2444
0
        appendSql(sqlStatements, sql);
2445
0
    }
2446
0
    identifyOrInsertUsages(ensemble,
2447
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2448
0
                           authName, code, allowedAuthorities, sqlStatements);
2449
2450
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2451
0
                                       : "vertical_datum_ensemble_member";
2452
0
    counter = 1;
2453
0
    for (const auto &authCodePair : membersId) {
2454
0
        const auto sql = formatStatement(
2455
0
            "INSERT INTO %s VALUES("
2456
0
            "'%q','%q','%q','%q',%d);",
2457
0
            tableName, authName.c_str(), code.c_str(),
2458
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2459
0
        appendSql(sqlStatements, sql);
2460
0
        ++counter;
2461
0
    }
2462
2463
0
    return sqlStatements;
2464
0
}
2465
2466
// ---------------------------------------------------------------------------
2467
2468
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2469
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2470
    const std::string &code, bool numericCode,
2471
0
    const std::vector<std::string> &allowedAuthorities) {
2472
2473
0
    const auto self = NN_NO_CHECK(self_.lock());
2474
2475
0
    std::vector<std::string> sqlStatements;
2476
2477
    // Find or insert datum/datum ensemble
2478
0
    std::string datumAuthName;
2479
0
    std::string datumCode;
2480
0
    const auto &ensemble = crs->datumEnsemble();
2481
0
    if (ensemble) {
2482
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2483
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2484
0
                               datumAuthName, datumCode);
2485
0
        if (datumAuthName.empty()) {
2486
0
            datumAuthName = authName;
2487
0
            if (numericCode) {
2488
0
                datumCode =
2489
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2490
0
            } else {
2491
0
                datumCode = "GEODETIC_DATUM_" + code;
2492
0
            }
2493
0
            sqlStatements = self->getInsertStatementsFor(
2494
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2495
0
                allowedAuthorities);
2496
0
        }
2497
0
    } else {
2498
0
        const auto &datum = crs->datum();
2499
0
        assert(datum);
2500
0
        const auto datumNN = NN_NO_CHECK(datum);
2501
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2502
0
                               datumAuthName, datumCode);
2503
0
        if (datumAuthName.empty()) {
2504
0
            datumAuthName = authName;
2505
0
            if (numericCode) {
2506
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2507
0
            } else {
2508
0
                datumCode = "GEODETIC_DATUM_" + code;
2509
0
            }
2510
0
            sqlStatements =
2511
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2512
0
                                             numericCode, allowedAuthorities);
2513
0
        }
2514
0
    }
2515
2516
    // Find or insert coordinate system
2517
0
    const auto &coordinateSystem = crs->coordinateSystem();
2518
0
    std::string csAuthName;
2519
0
    std::string csCode;
2520
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2521
0
                     csAuthName, csCode, sqlStatements);
2522
2523
0
    const char *type = GEOG_2D;
2524
0
    if (coordinateSystem->axisList().size() == 3) {
2525
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2526
0
            type = GEOG_3D;
2527
0
        } else {
2528
0
            type = GEOCENTRIC;
2529
0
        }
2530
0
    }
2531
2532
    // Insert new record in geodetic_crs table
2533
0
    const auto sql =
2534
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2535
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2536
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2537
0
                        "", // description
2538
0
                        type, csAuthName.c_str(), csCode.c_str(),
2539
0
                        datumAuthName.c_str(), datumCode.c_str());
2540
0
    appendSql(sqlStatements, sql);
2541
2542
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2543
0
                           allowedAuthorities, sqlStatements);
2544
0
    return sqlStatements;
2545
0
}
2546
2547
// ---------------------------------------------------------------------------
2548
2549
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2550
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2551
    const std::string &code, bool numericCode,
2552
0
    const std::vector<std::string> &allowedAuthorities) {
2553
2554
0
    const auto self = NN_NO_CHECK(self_.lock());
2555
2556
0
    std::vector<std::string> sqlStatements;
2557
2558
    // Find or insert base geodetic CRS
2559
0
    const auto &baseCRS = crs->baseCRS();
2560
0
    std::string geodAuthName;
2561
0
    std::string geodCode;
2562
2563
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2564
0
    allowedAuthoritiesTmp.emplace_back(authName);
2565
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2566
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2567
0
        const auto candidates = baseCRS->identify(factory);
2568
0
        for (const auto &candidate : candidates) {
2569
0
            if (candidate.second == 100) {
2570
0
                const auto &ids = candidate.first->identifiers();
2571
0
                if (!ids.empty()) {
2572
0
                    const auto &id = ids.front();
2573
0
                    geodAuthName = *(id->codeSpace());
2574
0
                    geodCode = id->code();
2575
0
                    break;
2576
0
                }
2577
0
            }
2578
0
            if (!geodAuthName.empty()) {
2579
0
                break;
2580
0
            }
2581
0
        }
2582
0
    }
2583
0
    if (geodAuthName.empty()) {
2584
0
        geodAuthName = authName;
2585
0
        geodCode = "GEODETIC_CRS_" + code;
2586
0
        sqlStatements = self->getInsertStatementsFor(
2587
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2588
0
    }
2589
2590
    // Insert new record in conversion table
2591
0
    const auto &conversion = crs->derivingConversionRef();
2592
0
    std::string convAuthName(authName);
2593
0
    std::string convCode("CONVERSION_" + code);
2594
0
    if (numericCode) {
2595
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2596
0
    }
2597
0
    {
2598
0
        const auto &method = conversion->method();
2599
0
        const auto &methodIds = method->identifiers();
2600
0
        std::string methodAuthName;
2601
0
        std::string methodCode;
2602
0
        const operation::MethodMapping *methodMapping = nullptr;
2603
0
        if (methodIds.empty()) {
2604
0
            const int epsgCode = method->getEPSGCode();
2605
0
            if (epsgCode > 0) {
2606
0
                methodAuthName = metadata::Identifier::EPSG;
2607
0
                methodCode = toString(epsgCode);
2608
0
            } else {
2609
0
                const auto &methodName = method->nameStr();
2610
0
                size_t nProjectionMethodMappings = 0;
2611
0
                const auto projectionMethodMappings =
2612
0
                    operation::getProjectionMethodMappings(
2613
0
                        nProjectionMethodMappings);
2614
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2615
0
                    const auto &mapping = projectionMethodMappings[i];
2616
0
                    if (metadata::Identifier::isEquivalentName(
2617
0
                            mapping.wkt2_name, methodName.c_str())) {
2618
0
                        methodMapping = &mapping;
2619
0
                    }
2620
0
                }
2621
0
                if (methodMapping == nullptr ||
2622
0
                    methodMapping->proj_name_main == nullptr) {
2623
0
                    throw FactoryException("Cannot insert projection with "
2624
0
                                           "method without identifier");
2625
0
                }
2626
0
                methodAuthName = "PROJ";
2627
0
                methodCode = methodMapping->proj_name_main;
2628
0
                if (methodMapping->proj_name_aux) {
2629
0
                    methodCode += ' ';
2630
0
                    methodCode += methodMapping->proj_name_aux;
2631
0
                }
2632
0
            }
2633
0
        } else {
2634
0
            const auto &methodId = methodIds.front();
2635
0
            methodAuthName = *(methodId->codeSpace());
2636
0
            methodCode = methodId->code();
2637
0
        }
2638
2639
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2640
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2641
0
                                   convAuthName.c_str(), convCode.c_str(),
2642
0
                                   conversion->nameStr().c_str(),
2643
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2644
0
                                   method->nameStr().c_str());
2645
0
        const auto &srcValues = conversion->parameterValues();
2646
0
        if (srcValues.size() > N_MAX_PARAMS) {
2647
0
            throw FactoryException("Cannot insert projection with more than " +
2648
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2649
0
                                   " parameters");
2650
0
        }
2651
2652
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2653
0
        if (methodMapping == nullptr) {
2654
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2655
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2656
0
            } else {
2657
0
                methodMapping =
2658
0
                    operation::getMapping(method->nameStr().c_str());
2659
0
            }
2660
0
        }
2661
0
        if (methodMapping != nullptr) {
2662
            // Re-order projection parameters in their reference order
2663
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2664
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2665
0
                    auto opParamValue = dynamic_cast<
2666
0
                        const operation::OperationParameterValue *>(
2667
0
                        srcValues[i].get());
2668
0
                    if (!opParamValue) {
2669
0
                        throw FactoryException("Cannot insert projection with "
2670
0
                                               "non-OperationParameterValue");
2671
0
                    }
2672
0
                    if (methodMapping->params[j]->wkt2_name &&
2673
0
                        opParamValue->parameter()->nameStr() ==
2674
0
                            methodMapping->params[j]->wkt2_name) {
2675
0
                        values.emplace_back(srcValues[i]);
2676
0
                    }
2677
0
                }
2678
0
            }
2679
0
        }
2680
0
        if (values.size() != srcValues.size()) {
2681
0
            values = srcValues;
2682
0
        }
2683
2684
0
        for (const auto &genOpParamvalue : values) {
2685
0
            auto opParamValue =
2686
0
                dynamic_cast<const operation::OperationParameterValue *>(
2687
0
                    genOpParamvalue.get());
2688
0
            if (!opParamValue) {
2689
0
                throw FactoryException("Cannot insert projection with "
2690
0
                                       "non-OperationParameterValue");
2691
0
            }
2692
0
            const auto &param = opParamValue->parameter();
2693
0
            const auto &paramIds = param->identifiers();
2694
0
            std::string paramAuthName;
2695
0
            std::string paramCode;
2696
0
            if (paramIds.empty()) {
2697
0
                const int paramEPSGCode = param->getEPSGCode();
2698
0
                if (paramEPSGCode == 0) {
2699
0
                    throw FactoryException(
2700
0
                        "Cannot insert projection with method parameter "
2701
0
                        "without identifier");
2702
0
                }
2703
0
                paramAuthName = metadata::Identifier::EPSG;
2704
0
                paramCode = toString(paramEPSGCode);
2705
0
            } else {
2706
0
                const auto &paramId = paramIds.front();
2707
0
                paramAuthName = *(paramId->codeSpace());
2708
0
                paramCode = paramId->code();
2709
0
            }
2710
0
            const auto &value = opParamValue->parameterValue()->value();
2711
0
            const auto &unit = value.unit();
2712
0
            std::string uomAuthName;
2713
0
            std::string uomCode;
2714
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2715
0
                             sqlStatements);
2716
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2717
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2718
0
                                   param->nameStr().c_str(), value.value(),
2719
0
                                   uomAuthName.c_str(), uomCode.c_str());
2720
0
        }
2721
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2722
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2723
0
        }
2724
0
        sql += ",0);";
2725
0
        appendSql(sqlStatements, sql);
2726
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2727
0
                               allowedAuthorities, sqlStatements);
2728
0
    }
2729
2730
    // Find or insert coordinate system
2731
0
    const auto &coordinateSystem = crs->coordinateSystem();
2732
0
    std::string csAuthName;
2733
0
    std::string csCode;
2734
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2735
0
                     csAuthName, csCode, sqlStatements);
2736
2737
    // Insert new record in projected_crs table
2738
0
    const auto sql = formatStatement(
2739
0
        "INSERT INTO projected_crs VALUES("
2740
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2741
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2742
0
        "", // description
2743
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2744
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2745
0
    appendSql(sqlStatements, sql);
2746
2747
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2748
0
                           allowedAuthorities, sqlStatements);
2749
2750
0
    return sqlStatements;
2751
0
}
2752
2753
// ---------------------------------------------------------------------------
2754
2755
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2756
    const datum::VerticalReferenceFrameNNPtr &datum,
2757
    const std::string &authName, const std::string &code,
2758
    bool /* numericCode */,
2759
0
    const std::vector<std::string> &allowedAuthorities) {
2760
2761
0
    const auto self = NN_NO_CHECK(self_.lock());
2762
2763
0
    std::vector<std::string> sqlStatements;
2764
2765
    // Check if the object is already known under that code
2766
0
    std::string datumAuthName;
2767
0
    std::string datumCode;
2768
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2769
0
                           datumAuthName, datumCode);
2770
0
    if (datumAuthName == authName && datumCode == code) {
2771
0
        return {};
2772
0
    }
2773
2774
    // Insert new record in vertical_datum table
2775
0
    std::string publicationDate("NULL");
2776
0
    if (datum->publicationDate().has_value()) {
2777
0
        publicationDate = '\'';
2778
0
        publicationDate +=
2779
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2780
0
        publicationDate += '\'';
2781
0
    }
2782
0
    std::string frameReferenceEpoch("NULL");
2783
0
    const auto dynamicDatum =
2784
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2785
0
    if (dynamicDatum) {
2786
0
        frameReferenceEpoch =
2787
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2788
0
    }
2789
0
    const std::string anchor(*(datum->anchorDefinition()));
2790
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2791
0
    const auto sql = formatStatement(
2792
0
        "INSERT INTO vertical_datum VALUES("
2793
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2794
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2795
0
        "", // description
2796
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2797
0
        anchor.empty() ? nullptr : anchor.c_str(),
2798
0
        anchorEpoch.has_value()
2799
0
            ? anchorEpochToStr(
2800
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2801
0
                  .c_str()
2802
0
            : "NULL");
2803
0
    appendSql(sqlStatements, sql);
2804
2805
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2806
0
                           allowedAuthorities, sqlStatements);
2807
2808
0
    return sqlStatements;
2809
0
}
2810
2811
// ---------------------------------------------------------------------------
2812
2813
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2814
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2815
    const std::string &code, bool numericCode,
2816
0
    const std::vector<std::string> &allowedAuthorities) {
2817
2818
0
    const auto self = NN_NO_CHECK(self_.lock());
2819
2820
0
    std::vector<std::string> sqlStatements;
2821
2822
    // Find or insert datum/datum ensemble
2823
0
    std::string datumAuthName;
2824
0
    std::string datumCode;
2825
0
    const auto &ensemble = crs->datumEnsemble();
2826
0
    if (ensemble) {
2827
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2828
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2829
0
                               datumAuthName, datumCode);
2830
0
        if (datumAuthName.empty()) {
2831
0
            datumAuthName = authName;
2832
0
            if (numericCode) {
2833
0
                datumCode =
2834
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2835
0
            } else {
2836
0
                datumCode = "VERTICAL_DATUM_" + code;
2837
0
            }
2838
0
            sqlStatements = self->getInsertStatementsFor(
2839
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2840
0
                allowedAuthorities);
2841
0
        }
2842
0
    } else {
2843
0
        const auto &datum = crs->datum();
2844
0
        assert(datum);
2845
0
        const auto datumNN = NN_NO_CHECK(datum);
2846
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2847
0
                               datumAuthName, datumCode);
2848
0
        if (datumAuthName.empty()) {
2849
0
            datumAuthName = authName;
2850
0
            if (numericCode) {
2851
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2852
0
            } else {
2853
0
                datumCode = "VERTICAL_DATUM_" + code;
2854
0
            }
2855
0
            sqlStatements =
2856
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2857
0
                                             numericCode, allowedAuthorities);
2858
0
        }
2859
0
    }
2860
2861
    // Find or insert coordinate system
2862
0
    const auto &coordinateSystem = crs->coordinateSystem();
2863
0
    std::string csAuthName;
2864
0
    std::string csCode;
2865
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2866
0
                     csAuthName, csCode, sqlStatements);
2867
2868
    // Insert new record in vertical_crs table
2869
0
    const auto sql =
2870
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2871
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2872
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2873
0
                        "", // description
2874
0
                        csAuthName.c_str(), csCode.c_str(),
2875
0
                        datumAuthName.c_str(), datumCode.c_str());
2876
0
    appendSql(sqlStatements, sql);
2877
2878
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2879
0
                           allowedAuthorities, sqlStatements);
2880
2881
0
    return sqlStatements;
2882
0
}
2883
2884
// ---------------------------------------------------------------------------
2885
2886
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2887
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2888
    const std::string &code, bool numericCode,
2889
0
    const std::vector<std::string> &allowedAuthorities) {
2890
2891
0
    const auto self = NN_NO_CHECK(self_.lock());
2892
2893
0
    std::vector<std::string> sqlStatements;
2894
2895
0
    int counter = 1;
2896
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2897
0
    const auto &components = crs->componentReferenceSystems();
2898
0
    if (components.size() != 2) {
2899
0
        throw FactoryException(
2900
0
            "Cannot insert compound CRS with number of components != 2");
2901
0
    }
2902
2903
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2904
0
    allowedAuthoritiesTmp.emplace_back(authName);
2905
2906
0
    for (const auto &component : components) {
2907
0
        std::string compAuthName;
2908
0
        std::string compCode;
2909
2910
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2911
0
            const auto factory =
2912
0
                AuthorityFactory::create(self, allowedAuthority);
2913
0
            const auto candidates = component->identify(factory);
2914
0
            for (const auto &candidate : candidates) {
2915
0
                if (candidate.second == 100) {
2916
0
                    const auto &ids = candidate.first->identifiers();
2917
0
                    if (!ids.empty()) {
2918
0
                        const auto &id = ids.front();
2919
0
                        compAuthName = *(id->codeSpace());
2920
0
                        compCode = id->code();
2921
0
                        break;
2922
0
                    }
2923
0
                }
2924
0
                if (!compAuthName.empty()) {
2925
0
                    break;
2926
0
                }
2927
0
            }
2928
0
        }
2929
2930
0
        if (compAuthName.empty()) {
2931
0
            compAuthName = authName;
2932
0
            if (numericCode) {
2933
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2934
0
            } else {
2935
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2936
0
            }
2937
0
            const auto sqlStatementsTmp =
2938
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2939
0
                                             numericCode, allowedAuthorities);
2940
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2941
0
                                 sqlStatementsTmp.end());
2942
0
        }
2943
2944
0
        componentsId.emplace_back(
2945
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2946
2947
0
        ++counter;
2948
0
    }
2949
2950
    // Insert new record in compound_crs table
2951
0
    const auto sql = formatStatement(
2952
0
        "INSERT INTO compound_crs VALUES("
2953
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2954
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2955
0
        "", // description
2956
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2957
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2958
0
    appendSql(sqlStatements, sql);
2959
2960
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2961
0
                           allowedAuthorities, sqlStatements);
2962
2963
0
    return sqlStatements;
2964
0
}
2965
2966
//! @endcond
2967
2968
// ---------------------------------------------------------------------------
2969
2970
//! @cond Doxygen_Suppress
2971
1.17k
DatabaseContext::~DatabaseContext() {
2972
1.17k
    try {
2973
1.17k
        stopInsertStatementsSession();
2974
1.17k
    } catch (const std::exception &) {
2975
0
    }
2976
1.17k
}
2977
//! @endcond
2978
2979
// ---------------------------------------------------------------------------
2980
2981
1.17k
DatabaseContext::DatabaseContext() : d(std::make_unique<Private>()) {}
2982
2983
// ---------------------------------------------------------------------------
2984
2985
/** \brief Instantiate a database context.
2986
 *
2987
 * This database context should be used only by one thread at a time.
2988
 *
2989
 * @param databasePath Path and filename of the database. Might be empty
2990
 * string for the default rules to locate the default proj.db
2991
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2992
 * Might be empty.
2993
 * Starting with PROJ 8.1, if this parameter is an empty array,
2994
 * the PROJ_AUX_DB environment variable will be used, if set.
2995
 * It must contain one or several paths. If several paths are
2996
 * provided, they must be separated by the colon (:) character on Unix, and
2997
 * on Windows, by the semi-colon (;) character.
2998
 * @param ctx Context used for file search.
2999
 * @throw FactoryException if the database cannot be opened.
3000
 */
3001
DatabaseContextNNPtr
3002
DatabaseContext::create(const std::string &databasePath,
3003
                        const std::vector<std::string> &auxiliaryDatabasePaths,
3004
1.17k
                        PJ_CONTEXT *ctx) {
3005
1.17k
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
3006
1.17k
    auto dbCtxPrivate = dbCtx->getPrivate();
3007
1.17k
    dbCtxPrivate->open(databasePath, ctx);
3008
1.17k
    auto auxDbs(auxiliaryDatabasePaths);
3009
1.17k
    if (auxDbs.empty()) {
3010
1.17k
        const char *auxDbStr = getenv("PROJ_AUX_DB");
3011
1.17k
        if (auxDbStr) {
3012
#ifdef _WIN32
3013
            const char *delim = ";";
3014
#else
3015
0
            const char *delim = ":";
3016
0
#endif
3017
0
            auxDbs = split(auxDbStr, delim);
3018
0
        }
3019
1.17k
    }
3020
1.17k
    if (!auxDbs.empty()) {
3021
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
3022
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
3023
0
    }
3024
1.17k
    dbCtxPrivate->self_ = dbCtx.as_nullable();
3025
1.17k
    return dbCtx;
3026
1.17k
}
3027
3028
// ---------------------------------------------------------------------------
3029
3030
/** \brief Return the list of authorities used in the database.
3031
 */
3032
700
std::set<std::string> DatabaseContext::getAuthorities() const {
3033
700
    auto res = d->run("SELECT auth_name FROM authority_list");
3034
700
    std::set<std::string> list;
3035
5.60k
    for (const auto &row : res) {
3036
5.60k
        list.insert(row[0]);
3037
5.60k
    }
3038
700
    return list;
3039
700
}
3040
3041
// ---------------------------------------------------------------------------
3042
3043
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
3044
 * CREATE VIEW) needed to initialize a new database.
3045
 */
3046
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
3047
0
    return d->getDatabaseStructure();
3048
0
}
3049
3050
// ---------------------------------------------------------------------------
3051
3052
/** \brief Return the path to the database.
3053
 */
3054
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
3055
3056
// ---------------------------------------------------------------------------
3057
3058
/** \brief Return a metadata item.
3059
 *
3060
 * Value remains valid while this is alive and to the next call to getMetadata
3061
 */
3062
0
const char *DatabaseContext::getMetadata(const char *key) const {
3063
0
    auto res =
3064
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
3065
0
    if (res.empty()) {
3066
0
        return nullptr;
3067
0
    }
3068
0
    d->lastMetadataValue_ = res.front()[0];
3069
0
    return d->lastMetadataValue_.c_str();
3070
0
}
3071
3072
// ---------------------------------------------------------------------------
3073
3074
/** \brief Starts a session for getInsertStatementsFor()
3075
 *
3076
 * Starts a new session for one or several calls to getInsertStatementsFor().
3077
 * An insertion session guarantees that the inserted objects will not create
3078
 * conflicting intermediate objects.
3079
 *
3080
 * The session must be stopped with stopInsertStatementsSession().
3081
 *
3082
 * Only one session may be active at a time for a given database context.
3083
 *
3084
 * @throw FactoryException in case of error.
3085
 * @since 8.1
3086
 */
3087
0
void DatabaseContext::startInsertStatementsSession() {
3088
0
    if (d->memoryDbHandle_) {
3089
0
        throw FactoryException(
3090
0
            "startInsertStatementsSession() cannot be invoked until "
3091
0
            "stopInsertStatementsSession() is.");
3092
0
    }
3093
3094
0
    d->memoryDbForInsertPath_.clear();
3095
0
    const auto sqlStatements = getDatabaseStructure();
3096
3097
    // Create a in-memory temporary sqlite3 database
3098
0
    std::ostringstream buffer;
3099
0
    buffer << "file:temp_db_for_insert_statements_";
3100
0
    buffer << this;
3101
0
    buffer << ".db?mode=memory&cache=shared";
3102
0
    d->memoryDbForInsertPath_ = buffer.str();
3103
0
    sqlite3 *memoryDbHandle = nullptr;
3104
0
    sqlite3_open_v2(
3105
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3106
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3107
0
    if (memoryDbHandle == nullptr) {
3108
0
        throw FactoryException("Cannot create in-memory database");
3109
0
    }
3110
0
    d->memoryDbHandle_ =
3111
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3112
3113
    // Fill the structure of this database
3114
0
    for (const auto &sql : sqlStatements) {
3115
0
        char *errmsg = nullptr;
3116
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3117
0
                         nullptr, &errmsg) != SQLITE_OK) {
3118
0
            const auto sErrMsg =
3119
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3120
0
            sqlite3_free(errmsg);
3121
0
            throw FactoryException(sErrMsg);
3122
0
        }
3123
0
        sqlite3_free(errmsg);
3124
0
    }
3125
3126
    // Attach this database to the current one(s)
3127
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3128
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3129
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3130
0
}
3131
3132
// ---------------------------------------------------------------------------
3133
3134
/** \brief Suggests a database code for the passed object.
3135
 *
3136
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3137
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3138
 *
3139
 * @param object Object for which to suggest a code.
3140
 * @param authName Authority name into which the object will be inserted.
3141
 * @param numericCode Whether the code should be numeric, or derived from the
3142
 * object name.
3143
 * @return the suggested code, that is guaranteed to not conflict with an
3144
 * existing one.
3145
 *
3146
 * @throw FactoryException in case of error.
3147
 * @since 8.1
3148
 */
3149
std::string
3150
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3151
                                 const std::string &authName,
3152
0
                                 bool numericCode) {
3153
0
    const char *tableName = "prime_meridian";
3154
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3155
        // tableName = "prime_meridian";
3156
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3157
0
        tableName = "ellipsoid";
3158
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3159
0
                   object.get())) {
3160
0
        tableName = "geodetic_datum";
3161
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3162
0
                   object.get())) {
3163
0
        tableName = "vertical_datum";
3164
0
    } else if (const auto ensemble =
3165
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3166
0
        const auto &datums = ensemble->datums();
3167
0
        if (!datums.empty() &&
3168
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3169
0
                datums[0].get())) {
3170
0
            tableName = "geodetic_datum";
3171
0
        } else {
3172
0
            tableName = "vertical_datum";
3173
0
        }
3174
0
    } else if (const auto boundCRS =
3175
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3176
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3177
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3178
0
        tableName = "crs_view";
3179
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3180
0
        tableName = "conversion";
3181
0
    } else {
3182
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3183
0
    }
3184
3185
0
    if (numericCode) {
3186
0
        std::string sql("SELECT MAX(code) FROM ");
3187
0
        sql += tableName;
3188
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3189
0
               "AND upper(code) = lower(code)";
3190
0
        const auto res = d->run(sql, {authName});
3191
0
        if (res.empty()) {
3192
0
            return "1";
3193
0
        }
3194
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3195
0
    }
3196
3197
0
    std::string code;
3198
0
    code.reserve(object->nameStr().size());
3199
0
    bool insertUnderscore = false;
3200
0
    for (const auto ch : toupper(object->nameStr())) {
3201
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3202
0
            if (insertUnderscore && code.back() != '_')
3203
0
                code += '_';
3204
0
            code += ch;
3205
0
            insertUnderscore = false;
3206
0
        } else {
3207
0
            insertUnderscore = true;
3208
0
        }
3209
0
    }
3210
0
    return d->findFreeCode(tableName, authName, code);
3211
0
}
3212
3213
// ---------------------------------------------------------------------------
3214
3215
/** \brief Returns SQL statements needed to insert the passed object into the
3216
 * database.
3217
 *
3218
 * startInsertStatementsSession() must have been called previously.
3219
 *
3220
 * @param object The object to insert into the database. Currently only
3221
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3222
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3223
 * @param authName Authority name into which the object will be inserted.
3224
 * @param code Code with which the object will be inserted.
3225
 * @param numericCode Whether intermediate objects that can be created should
3226
 *                    use numeric codes (true), or may be alphanumeric (false)
3227
 * @param allowedAuthorities Authorities to which intermediate objects are
3228
 *                           allowed to refer to. authName will be implicitly
3229
 *                           added to it. Note that unit, coordinate
3230
 *                           systems, projection methods and parameters will in
3231
 *                           any case be allowed to refer to EPSG.
3232
 * @throw FactoryException in case of error.
3233
 * @since 8.1
3234
 */
3235
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3236
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3237
    const std::string &code, bool numericCode,
3238
0
    const std::vector<std::string> &allowedAuthorities) {
3239
0
    if (d->memoryDbHandle_ == nullptr) {
3240
0
        throw FactoryException(
3241
0
            "startInsertStatementsSession() should be invoked first");
3242
0
    }
3243
3244
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3245
0
    if (crs) {
3246
        // Check if the object is already known under that code
3247
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3248
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3249
0
        allowedAuthoritiesTmp.emplace_back(authName);
3250
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3251
0
            const auto factory =
3252
0
                AuthorityFactory::create(self, allowedAuthority);
3253
0
            const auto candidates = crs->identify(factory);
3254
0
            for (const auto &candidate : candidates) {
3255
0
                if (candidate.second == 100) {
3256
0
                    const auto &ids = candidate.first->identifiers();
3257
0
                    for (const auto &id : ids) {
3258
0
                        if (*(id->codeSpace()) == authName &&
3259
0
                            id->code() == code) {
3260
0
                            return {};
3261
0
                        }
3262
0
                    }
3263
0
                }
3264
0
            }
3265
0
        }
3266
0
    }
3267
3268
0
    if (const auto pm =
3269
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3270
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3271
0
                                         numericCode, allowedAuthorities);
3272
0
    }
3273
3274
0
    else if (const auto ellipsoid =
3275
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3276
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3277
0
                                         numericCode, allowedAuthorities);
3278
0
    }
3279
3280
0
    else if (const auto geodeticDatum =
3281
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3282
0
                     object)) {
3283
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3284
0
                                         code, numericCode, allowedAuthorities);
3285
0
    }
3286
3287
0
    else if (const auto ensemble =
3288
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3289
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3290
0
                                         numericCode, allowedAuthorities);
3291
0
    }
3292
3293
0
    else if (const auto geodCRS =
3294
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3295
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3296
0
                                         numericCode, allowedAuthorities);
3297
0
    }
3298
3299
0
    else if (const auto projCRS =
3300
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3301
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3302
0
                                         numericCode, allowedAuthorities);
3303
0
    }
3304
3305
0
    else if (const auto verticalDatum =
3306
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3307
0
                     object)) {
3308
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3309
0
                                         code, numericCode, allowedAuthorities);
3310
0
    }
3311
3312
0
    else if (const auto vertCRS =
3313
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3314
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3315
0
                                         numericCode, allowedAuthorities);
3316
0
    }
3317
3318
0
    else if (const auto compoundCRS =
3319
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3320
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3321
0
                                         code, numericCode, allowedAuthorities);
3322
0
    }
3323
3324
0
    else if (const auto boundCRS =
3325
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3326
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3327
0
                                      numericCode, allowedAuthorities);
3328
0
    }
3329
3330
0
    else {
3331
0
        throw FactoryException(
3332
0
            "getInsertStatementsFor(): unhandled type of object");
3333
0
    }
3334
0
}
3335
3336
// ---------------------------------------------------------------------------
3337
3338
/** \brief Stops an insertion session started with
3339
 * startInsertStatementsSession()
3340
 *
3341
 * @since 8.1
3342
 */
3343
1.17k
void DatabaseContext::stopInsertStatementsSession() {
3344
1.17k
    if (d->memoryDbHandle_) {
3345
0
        d->clearCaches();
3346
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3347
0
        d->memoryDbHandle_.reset();
3348
0
        d->memoryDbForInsertPath_.clear();
3349
0
    }
3350
1.17k
}
3351
3352
// ---------------------------------------------------------------------------
3353
3354
//! @cond Doxygen_Suppress
3355
3356
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3357
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3358
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3359
0
    return ctxt;
3360
0
}
3361
3362
// ---------------------------------------------------------------------------
3363
3364
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3365
3366
// ---------------------------------------------------------------------------
3367
3368
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3369
                                             std::string &projFilename,
3370
                                             std::string &projFormat,
3371
371
                                             bool &inverse) const {
3372
371
    auto res = d->run(
3373
371
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3374
371
        "grid_alternatives WHERE original_grid_name = ? AND "
3375
371
        "proj_grid_name <> ''",
3376
371
        {officialName});
3377
371
    if (res.empty()) {
3378
97
        return false;
3379
97
    }
3380
274
    const auto &row = res.front();
3381
274
    projFilename = row[0];
3382
274
    projFormat = row[1];
3383
274
    inverse = row[2] == "1";
3384
274
    return true;
3385
371
}
3386
3387
// ---------------------------------------------------------------------------
3388
3389
static std::string makeCachedGridKey(const std::string &projFilename,
3390
                                     bool networkEnabled,
3391
0
                                     bool considerKnownGridsAsAvailable) {
3392
0
    std::string key(projFilename);
3393
0
    key += networkEnabled ? "true" : "false";
3394
0
    key += considerKnownGridsAsAvailable ? "true" : "false";
3395
0
    return key;
3396
0
}
3397
3398
// ---------------------------------------------------------------------------
3399
3400
/** Invalidates information related to projFilename that might have been
3401
 * previously cached by lookForGridInfo().
3402
 *
3403
 * This is useful when downloading a new grid during a session.
3404
 */
3405
0
void DatabaseContext::invalidateGridInfo(const std::string &projFilename) {
3406
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false));
3407
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true));
3408
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false));
3409
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true));
3410
0
}
3411
3412
// ---------------------------------------------------------------------------
3413
3414
bool DatabaseContext::lookForGridInfo(
3415
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3416
    std::string &fullFilename, std::string &packageName, std::string &url,
3417
0
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3418
0
    Private::GridInfoCache info;
3419
3420
0
    if (projFilename == "null") {
3421
        // Special case for implicit "null" grid.
3422
0
        fullFilename.clear();
3423
0
        packageName.clear();
3424
0
        url.clear();
3425
0
        directDownload = false;
3426
0
        openLicense = true;
3427
0
        gridAvailable = true;
3428
0
        return true;
3429
0
    }
3430
3431
0
    auto ctxt = d->pjCtxt();
3432
0
    if (ctxt == nullptr) {
3433
0
        ctxt = pj_get_default_ctx();
3434
0
        d->setPjCtxt(ctxt);
3435
0
    }
3436
3437
0
    const std::string key(makeCachedGridKey(
3438
0
        projFilename, proj_context_is_network_enabled(ctxt) != false,
3439
0
        considerKnownGridsAsAvailable));
3440
0
    if (d->getGridInfoFromCache(key, info)) {
3441
0
        fullFilename = info.fullFilename;
3442
0
        packageName = info.packageName;
3443
0
        url = info.url;
3444
0
        directDownload = info.directDownload;
3445
0
        openLicense = info.openLicense;
3446
0
        gridAvailable = info.gridAvailable;
3447
0
        return info.found;
3448
0
    }
3449
3450
0
    fullFilename.clear();
3451
0
    packageName.clear();
3452
0
    url.clear();
3453
0
    openLicense = false;
3454
0
    directDownload = false;
3455
0
    gridAvailable = false;
3456
3457
0
    const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() {
3458
0
        fullFilename.resize(2048);
3459
0
        const int errno_before = proj_context_errno(ctxt);
3460
0
        bool lGridAvailable = NS_PROJ::FileManager::open_resource_file(
3461
0
                                  ctxt, projFilename.c_str(), &fullFilename[0],
3462
0
                                  fullFilename.size() - 1) != nullptr;
3463
0
        proj_context_errno_set(ctxt, errno_before);
3464
0
        fullFilename.resize(strlen(fullFilename.c_str()));
3465
0
        return lGridAvailable;
3466
0
    };
3467
3468
0
    auto res =
3469
0
        d->run("SELECT "
3470
0
               "grid_packages.package_name, "
3471
0
               "grid_alternatives.url, "
3472
0
               "grid_packages.url AS package_url, "
3473
0
               "grid_alternatives.open_license, "
3474
0
               "grid_packages.open_license AS package_open_license, "
3475
0
               "grid_alternatives.direct_download, "
3476
0
               "grid_packages.direct_download AS package_direct_download, "
3477
0
               "grid_alternatives.proj_grid_name, "
3478
0
               "grid_alternatives.old_proj_grid_name "
3479
0
               "FROM grid_alternatives "
3480
0
               "LEFT JOIN grid_packages ON "
3481
0
               "grid_alternatives.package_name = grid_packages.package_name "
3482
0
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3483
0
               {projFilename, projFilename});
3484
0
    bool ret = !res.empty();
3485
0
    if (ret) {
3486
0
        const auto &row = res.front();
3487
0
        packageName = std::move(row[0]);
3488
0
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3489
0
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3490
0
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3491
3492
0
        const auto &proj_grid_name = row[7];
3493
0
        const auto &old_proj_grid_name = row[8];
3494
0
        if (proj_grid_name != old_proj_grid_name &&
3495
0
            old_proj_grid_name == projFilename) {
3496
0
            std::string fullFilenameNewName;
3497
0
            fullFilenameNewName.resize(2048);
3498
0
            const int errno_before = proj_context_errno(ctxt);
3499
0
            bool gridAvailableWithNewName =
3500
0
                pj_find_file(ctxt, proj_grid_name.c_str(),
3501
0
                             &fullFilenameNewName[0],
3502
0
                             fullFilenameNewName.size() - 1) != 0;
3503
0
            proj_context_errno_set(ctxt, errno_before);
3504
0
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3505
0
            if (gridAvailableWithNewName) {
3506
0
                gridAvailable = true;
3507
0
                fullFilename = std::move(fullFilenameNewName);
3508
0
            }
3509
0
        }
3510
3511
0
        if (!gridAvailable && considerKnownGridsAsAvailable &&
3512
0
            (!packageName.empty() || (!url.empty() && openLicense))) {
3513
3514
            // In considerKnownGridsAsAvailable mode, try to fetch the local
3515
            // file name if it exists, but do not attempt network access.
3516
0
            const auto network_was_enabled =
3517
0
                proj_context_is_network_enabled(ctxt);
3518
0
            proj_context_set_enable_network(ctxt, false);
3519
0
            (void)resolveFullFilename();
3520
0
            proj_context_set_enable_network(ctxt, network_was_enabled);
3521
3522
0
            gridAvailable = true;
3523
0
        }
3524
3525
0
        info.packageName = packageName;
3526
0
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3527
0
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3528
0
            if (endpoint.back() != '/') {
3529
0
                endpoint += '/';
3530
0
            }
3531
0
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3532
0
        }
3533
0
        info.directDownload = directDownload;
3534
0
        info.openLicense = openLicense;
3535
3536
0
        if (!gridAvailable) {
3537
0
            gridAvailable = resolveFullFilename();
3538
0
        }
3539
0
    } else {
3540
0
        gridAvailable = resolveFullFilename();
3541
3542
0
        if (starts_with(fullFilename, "http://") ||
3543
0
            starts_with(fullFilename, "https://")) {
3544
0
            url = fullFilename;
3545
0
            fullFilename.clear();
3546
0
        }
3547
0
    }
3548
3549
0
    info.fullFilename = fullFilename;
3550
0
    info.url = url;
3551
0
    info.gridAvailable = gridAvailable;
3552
0
    info.found = ret;
3553
0
    d->cache(key, info);
3554
0
    return ret;
3555
0
}
3556
3557
// ---------------------------------------------------------------------------
3558
3559
/** Returns the number of queries to the database since the creation of this
3560
 * instance.
3561
 */
3562
0
unsigned int DatabaseContext::getQueryCounter() const {
3563
0
    return d->queryCounter_;
3564
0
}
3565
3566
// ---------------------------------------------------------------------------
3567
3568
bool DatabaseContext::isKnownName(const std::string &name,
3569
0
                                  const std::string &tableName) const {
3570
0
    std::string sql("SELECT 1 FROM \"");
3571
0
    sql += replaceAll(tableName, "\"", "\"\"");
3572
0
    sql += "\" WHERE name = ? LIMIT 1";
3573
0
    return !d->run(sql, {name}).empty();
3574
0
}
3575
// ---------------------------------------------------------------------------
3576
3577
std::string
3578
25.7k
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3579
25.7k
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3580
25.7k
                      "old_proj_grid_name = ?",
3581
25.7k
                      {oldProjGridName});
3582
25.7k
    if (res.empty()) {
3583
25.7k
        return std::string();
3584
25.7k
    }
3585
4
    return res.front()[0];
3586
25.7k
}
3587
3588
// ---------------------------------------------------------------------------
3589
3590
2.96k
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3591
2.96k
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3592
2.96k
                      "proj_grid_name = ?",
3593
2.96k
                      {gridName});
3594
2.96k
    if (res.empty()) {
3595
2.94k
        return std::string();
3596
2.94k
    }
3597
13
    return res.front()[0];
3598
2.96k
}
3599
3600
// ---------------------------------------------------------------------------
3601
3602
// scripts/build_db_from_esri.py adds a second alias for
3603
// names that have '[' in them. See get_old_esri_name()
3604
// in scripts/build_db_from_esri.py
3605
// So if we only have two aliases detect that situation to get the official
3606
// new name
3607
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3608
0
    std::string first = l.front();
3609
0
    std::string second = *(std::next(l.begin()));
3610
0
    if (second.find('[') != std::string::npos)
3611
0
        std::swap(first, second);
3612
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3613
0
        second) {
3614
0
        return first;
3615
0
    }
3616
0
    return std::string();
3617
0
}
3618
3619
// ---------------------------------------------------------------------------
3620
3621
/** \brief Gets the alias name from an official name.
3622
 *
3623
 * @param officialName Official name. Mandatory
3624
 * @param tableName Table name/category. Mandatory.
3625
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3626
 *                  accepted as special names to add a constraint on the "type"
3627
 *                  column of the "geodetic_crs" table.
3628
 * @param source Source of the alias. Mandatory
3629
 * @return Alias name (or empty if not found).
3630
 * @throw FactoryException in case of error.
3631
 */
3632
std::string
3633
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3634
                                          const std::string &tableName,
3635
0
                                          const std::string &source) const {
3636
0
    std::string sql("SELECT auth_name, code FROM \"");
3637
0
    const auto genuineTableName =
3638
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3639
0
            ? "geodetic_crs"
3640
0
            : tableName;
3641
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3642
0
    sql += "\" WHERE name = ?";
3643
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3644
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3645
0
    } else if (tableName == "geographic_3D_crs") {
3646
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3647
0
    }
3648
0
    sql += " ORDER BY deprecated";
3649
0
    auto res = d->run(sql, {officialName});
3650
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3651
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3652
    // NAD83(HARN), and that's definitely not desirable.
3653
0
    if (res.empty() &&
3654
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3655
0
        res = d->run(
3656
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3657
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3658
0
            {genuineTableName, officialName});
3659
0
        if (res.size() != 1) {
3660
0
            return std::string();
3661
0
        }
3662
0
    }
3663
0
    for (const auto &row : res) {
3664
0
        auto res2 =
3665
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3666
0
                   "auth_name = ? AND code = ? AND source = ?",
3667
0
                   {genuineTableName, row[0], row[1], source});
3668
0
        if (!res2.empty()) {
3669
0
            if (res2.size() == 2 && source == "ESRI") {
3670
0
                std::list<std::string> l;
3671
0
                l.emplace_back(res2.front()[0]);
3672
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3673
0
                std::string uniqueEsriAlias = getUniqueEsriAlias(l);
3674
0
                if (!uniqueEsriAlias.empty())
3675
0
                    return uniqueEsriAlias;
3676
0
            }
3677
0
            return res2.front()[0];
3678
0
        }
3679
0
    }
3680
0
    return std::string();
3681
0
}
3682
3683
// ---------------------------------------------------------------------------
3684
3685
/** \brief Gets the alias names for an object.
3686
 *
3687
 * Either authName + code or officialName must be non empty.
3688
 *
3689
 * @param authName Authority.
3690
 * @param code Code.
3691
 * @param officialName Official name.
3692
 * @param tableName Table name/category. Mandatory.
3693
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3694
 *                  accepted as special names to add a constraint on the "type"
3695
 *                  column of the "geodetic_crs" table.
3696
 * @param source Source of the alias. May be empty.
3697
 * @return Aliases
3698
 */
3699
std::list<std::string> DatabaseContext::getAliases(
3700
    const std::string &authName, const std::string &code,
3701
    const std::string &officialName, const std::string &tableName,
3702
652
    const std::string &source) const {
3703
3704
652
    std::list<std::string> res;
3705
652
    const auto key(authName + code + officialName + tableName + source);
3706
652
    if (d->cacheAliasNames_.tryGet(key, res)) {
3707
624
        return res;
3708
624
    }
3709
3710
28
    std::string resolvedAuthName(authName);
3711
28
    std::string resolvedCode(code);
3712
28
    const auto genuineTableName =
3713
28
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3714
28
            ? "geodetic_crs"
3715
28
            : tableName;
3716
28
    if (authName.empty() || code.empty()) {
3717
0
        std::string sql("SELECT auth_name, code FROM \"");
3718
0
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3719
0
        sql += "\" WHERE name = ?";
3720
0
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3721
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3722
0
        } else if (tableName == "geographic_3D_crs") {
3723
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3724
0
        }
3725
0
        sql += " ORDER BY deprecated";
3726
0
        auto resSql = d->run(sql, {officialName});
3727
0
        if (resSql.empty()) {
3728
0
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3729
0
                            "table_name = ? AND "
3730
0
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3731
0
                            {genuineTableName, officialName});
3732
0
            if (resSql.size() != 1) {
3733
0
                d->cacheAliasNames_.insert(key, res);
3734
0
                return res;
3735
0
            }
3736
0
        }
3737
0
        const auto &row = resSql.front();
3738
0
        resolvedAuthName = row[0];
3739
0
        resolvedCode = row[1];
3740
0
    }
3741
28
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3742
28
                    "auth_name = ? AND code = ?");
3743
28
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3744
28
    if (source == "not EPSG_OLD") {
3745
28
        sql += " AND source != 'EPSG_OLD'";
3746
28
    } else if (!source.empty()) {
3747
0
        sql += " AND source = ?";
3748
0
        params.emplace_back(source);
3749
0
    }
3750
28
    auto resSql = d->run(sql, params);
3751
53
    for (const auto &row : resSql) {
3752
53
        res.emplace_back(row[0]);
3753
53
    }
3754
3755
28
    if (res.size() == 2 && source == "ESRI") {
3756
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3757
0
        if (!uniqueEsriAlias.empty()) {
3758
0
            res.clear();
3759
0
            res.emplace_back(uniqueEsriAlias);
3760
0
        }
3761
0
    }
3762
3763
28
    d->cacheAliasNames_.insert(key, res);
3764
28
    return res;
3765
28
}
3766
3767
// ---------------------------------------------------------------------------
3768
3769
/** \brief Return the 'name' column of a table for an object
3770
 *
3771
 * @param tableName Table name/category.
3772
 * @param authName Authority name of the object.
3773
 * @param code Code of the object
3774
 * @return Name (or empty)
3775
 * @throw FactoryException in case of error.
3776
 */
3777
std::string DatabaseContext::getName(const std::string &tableName,
3778
                                     const std::string &authName,
3779
0
                                     const std::string &code) const {
3780
0
    std::string res;
3781
0
    const auto key(tableName + authName + code);
3782
0
    if (d->cacheNames_.tryGet(key, res)) {
3783
0
        return res;
3784
0
    }
3785
3786
0
    std::string sql("SELECT name FROM \"");
3787
0
    sql += replaceAll(tableName, "\"", "\"\"");
3788
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3789
0
    auto sqlRes = d->run(sql, {authName, code});
3790
0
    if (sqlRes.empty()) {
3791
0
        res.clear();
3792
0
    } else {
3793
0
        res = sqlRes.front()[0];
3794
0
    }
3795
0
    d->cacheNames_.insert(key, res);
3796
0
    return res;
3797
0
}
3798
3799
// ---------------------------------------------------------------------------
3800
3801
/** \brief Return the 'text_definition' column of a table for an object
3802
 *
3803
 * @param tableName Table name/category.
3804
 * @param authName Authority name of the object.
3805
 * @param code Code of the object
3806
 * @return Text definition (or empty)
3807
 * @throw FactoryException in case of error.
3808
 */
3809
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3810
                                               const std::string &authName,
3811
0
                                               const std::string &code) const {
3812
0
    std::string sql("SELECT text_definition FROM \"");
3813
0
    sql += replaceAll(tableName, "\"", "\"\"");
3814
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3815
0
    auto res = d->run(sql, {authName, code});
3816
0
    if (res.empty()) {
3817
0
        return std::string();
3818
0
    }
3819
0
    return res.front()[0];
3820
0
}
3821
3822
// ---------------------------------------------------------------------------
3823
3824
/** \brief Return the allowed authorities when researching transformations
3825
 * between different authorities.
3826
 *
3827
 * @throw FactoryException in case of error.
3828
 */
3829
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3830
    const std::string &sourceAuthName,
3831
0
    const std::string &targetAuthName) const {
3832
3833
0
    const auto key(sourceAuthName + targetAuthName);
3834
0
    auto hit = d->cacheAllowedAuthorities_.find(key);
3835
0
    if (hit != d->cacheAllowedAuthorities_.end()) {
3836
0
        return hit->second;
3837
0
    }
3838
3839
0
    auto sqlRes = d->run(
3840
0
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3841
0
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3842
0
        {sourceAuthName, targetAuthName});
3843
0
    if (sqlRes.empty()) {
3844
0
        sqlRes = d->run(
3845
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3846
0
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3847
0
            {sourceAuthName});
3848
0
    }
3849
0
    if (sqlRes.empty()) {
3850
0
        sqlRes = d->run(
3851
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3852
0
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3853
0
            {targetAuthName});
3854
0
    }
3855
0
    if (sqlRes.empty()) {
3856
0
        sqlRes = d->run(
3857
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3858
0
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3859
0
            {});
3860
0
    }
3861
0
    if (sqlRes.empty()) {
3862
0
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3863
0
        return std::vector<std::string>();
3864
0
    }
3865
0
    auto res = split(sqlRes.front()[0], ',');
3866
0
    d->cacheAllowedAuthorities_[key] = res;
3867
0
    return res;
3868
0
}
3869
3870
// ---------------------------------------------------------------------------
3871
3872
std::list<std::pair<std::string, std::string>>
3873
DatabaseContext::getNonDeprecated(const std::string &tableName,
3874
                                  const std::string &authName,
3875
0
                                  const std::string &code) const {
3876
0
    auto sqlRes =
3877
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3878
0
               "FROM deprecation "
3879
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3880
0
               "AND deprecated_code = ?",
3881
0
               {tableName, authName, code});
3882
0
    std::list<std::pair<std::string, std::string>> res;
3883
0
    for (const auto &row : sqlRes) {
3884
0
        const auto &source = row[2];
3885
0
        if (source == "PROJ") {
3886
0
            const auto &replacement_auth_name = row[0];
3887
0
            const auto &replacement_code = row[1];
3888
0
            res.emplace_back(replacement_auth_name, replacement_code);
3889
0
        }
3890
0
    }
3891
0
    if (!res.empty()) {
3892
0
        return res;
3893
0
    }
3894
0
    for (const auto &row : sqlRes) {
3895
0
        const auto &replacement_auth_name = row[0];
3896
0
        const auto &replacement_code = row[1];
3897
0
        res.emplace_back(replacement_auth_name, replacement_code);
3898
0
    }
3899
0
    return res;
3900
0
}
3901
3902
// ---------------------------------------------------------------------------
3903
3904
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3905
3.33k
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3906
3.33k
    if (cacheAuthNameWithVersion_.empty()) {
3907
77
        const auto sqlRes =
3908
77
            run("SELECT versioned_auth_name, auth_name, version, priority "
3909
77
                "FROM versioned_auth_name_mapping");
3910
77
        for (const auto &row : sqlRes) {
3911
77
            VersionedAuthName van;
3912
77
            van.versionedAuthName = row[0];
3913
77
            van.authName = row[1];
3914
77
            van.version = row[2];
3915
77
            van.priority = atoi(row[3].c_str());
3916
77
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3917
77
        }
3918
77
    }
3919
3.33k
    return cacheAuthNameWithVersion_;
3920
3.33k
}
3921
3922
// ---------------------------------------------------------------------------
3923
3924
// From IAU_2015 returns (IAU,2015)
3925
bool DatabaseContext::getAuthorityAndVersion(
3926
    const std::string &versionedAuthName, std::string &authNameOut,
3927
0
    std::string &versionOut) {
3928
3929
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3930
0
        if (van.versionedAuthName == versionedAuthName) {
3931
0
            authNameOut = van.authName;
3932
0
            versionOut = van.version;
3933
0
            return true;
3934
0
        }
3935
0
    }
3936
0
    return false;
3937
0
}
3938
3939
// ---------------------------------------------------------------------------
3940
3941
// From IAU and 2015, returns IAU_2015
3942
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3943
                                            const std::string &version,
3944
2.63k
                                            std::string &versionedAuthNameOut) {
3945
3946
2.63k
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3947
2.63k
        if (van.authName == authName && van.version == version) {
3948
0
            versionedAuthNameOut = van.versionedAuthName;
3949
0
            return true;
3950
0
        }
3951
2.63k
    }
3952
2.63k
    return false;
3953
2.63k
}
3954
3955
// ---------------------------------------------------------------------------
3956
3957
// From IAU returns IAU_latest, ... IAU_2015
3958
std::vector<std::string>
3959
703
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3960
3961
703
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3962
703
    std::vector<VersionedAuthNamePriority> tmp;
3963
703
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3964
703
        if (van.authName == authName) {
3965
0
            tmp.emplace_back(
3966
0
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3967
0
        }
3968
703
    }
3969
703
    std::vector<std::string> res;
3970
703
    if (!tmp.empty()) {
3971
        // Sort by decreasing priority
3972
0
        std::sort(tmp.begin(), tmp.end(),
3973
0
                  [](const VersionedAuthNamePriority &a,
3974
0
                     const VersionedAuthNamePriority &b) {
3975
0
                      return b.second > a.second;
3976
0
                  });
3977
0
        for (const auto &pair : tmp)
3978
0
            res.emplace_back(pair.first);
3979
0
    }
3980
703
    return res;
3981
703
}
3982
3983
// ---------------------------------------------------------------------------
3984
3985
std::vector<operation::CoordinateOperationNNPtr>
3986
DatabaseContext::getTransformationsForGridName(
3987
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3988
0
    auto sqlRes = databaseContext->d->run(
3989
0
        "SELECT auth_name, code FROM grid_transformation "
3990
0
        "WHERE grid_name = ? OR grid_name IN "
3991
0
        "(SELECT original_grid_name FROM grid_alternatives "
3992
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3993
0
        {gridName, gridName});
3994
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3995
0
    for (const auto &row : sqlRes) {
3996
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3997
0
                             ->createCoordinateOperation(row[1], true));
3998
0
    }
3999
0
    return res;
4000
0
}
4001
4002
// ---------------------------------------------------------------------------
4003
4004
// Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame
4005
// transformation, where they neglect to reverse the sign of the rotation terms.
4006
// Cf https://github.com/OSGeo/PROJ/issues/4170 and
4007
// https://github.com/maptiler/epsg.io/issues/194
4008
// We do that only when we found a valid Coordinate Frame rotation that
4009
// has the same numeric values (and no corresponding Position Vector
4010
// transformation with same values, or Coordinate Frame transformation with
4011
// opposite sign for rotation terms, both are highly unlikely)
4012
bool DatabaseContext::toWGS84AutocorrectWrongValues(
4013
    double &tx, double &ty, double &tz, double &rx, double &ry, double &rz,
4014
1.60k
    double &scale_difference) const {
4015
1.60k
    if (rx == 0 && ry == 0 && rz == 0)
4016
1.58k
        return false;
4017
    // 9606: Position Vector transformation (geog2D domain)
4018
    // 9607: Coordinate Frame rotation (geog2D domain)
4019
20
    std::string sql(
4020
20
        "SELECT DISTINCT method_code "
4021
20
        "FROM helmert_transformation_table WHERE "
4022
20
        "abs(tx - ?) <= 1e-8 * abs(tx) AND "
4023
20
        "abs(ty - ?) <= 1e-8 * abs(ty) AND "
4024
20
        "abs(tz - ?) <= 1e-8 * abs(tz) AND "
4025
20
        "abs(rx - ?) <= 1e-8 * abs(rx) AND "
4026
20
        "abs(ry - ?) <= 1e-8 * abs(ry) AND "
4027
20
        "abs(rz - ?) <= 1e-8 * abs(rz) AND "
4028
20
        "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND "
4029
20
        "method_auth_name = 'EPSG' AND "
4030
20
        "method_code IN (9606, 9607) AND "
4031
20
        "translation_uom_auth_name = 'EPSG' AND "
4032
20
        "translation_uom_code = 9001 AND " // metre
4033
20
        "rotation_uom_auth_name = 'EPSG' AND "
4034
20
        "rotation_uom_code = 9104 AND " // arc-second
4035
20
        "scale_difference_uom_auth_name = 'EPSG' AND "
4036
20
        "scale_difference_uom_code = 9202 AND " // parts per million
4037
20
        "deprecated = 0");
4038
20
    ListOfParams params;
4039
20
    params.emplace_back(tx);
4040
20
    params.emplace_back(ty);
4041
20
    params.emplace_back(tz);
4042
20
    params.emplace_back(rx);
4043
20
    params.emplace_back(ry);
4044
20
    params.emplace_back(rz);
4045
20
    params.emplace_back(scale_difference);
4046
20
    bool bFound9606 = false;
4047
20
    bool bFound9607 = false;
4048
20
    for (const auto &row : d->run(sql, params)) {
4049
0
        if (row[0] == "9606") {
4050
0
            bFound9606 = true;
4051
0
        } else if (row[0] == "9607") {
4052
0
            bFound9607 = true;
4053
0
        }
4054
0
    }
4055
20
    if (bFound9607 && !bFound9606) {
4056
0
        params.clear();
4057
0
        params.emplace_back(tx);
4058
0
        params.emplace_back(ty);
4059
0
        params.emplace_back(tz);
4060
0
        params.emplace_back(-rx);
4061
0
        params.emplace_back(-ry);
4062
0
        params.emplace_back(-rz);
4063
0
        params.emplace_back(scale_difference);
4064
0
        if (d->run(sql, params).empty()) {
4065
0
            if (d->pjCtxt()) {
4066
0
                pj_log(d->pjCtxt(), PJ_LOG_ERROR,
4067
0
                       "Auto-correcting wrong sign of rotation terms of "
4068
0
                       "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to "
4069
0
                       "%s,%s,%s,%s,%s,%s,%s",
4070
0
                       internal::toString(tx).c_str(),
4071
0
                       internal::toString(ty).c_str(),
4072
0
                       internal::toString(tz).c_str(),
4073
0
                       internal::toString(rx).c_str(),
4074
0
                       internal::toString(ry).c_str(),
4075
0
                       internal::toString(rz).c_str(),
4076
0
                       internal::toString(scale_difference).c_str(),
4077
0
                       internal::toString(tx).c_str(),
4078
0
                       internal::toString(ty).c_str(),
4079
0
                       internal::toString(tz).c_str(),
4080
0
                       internal::toString(-rx).c_str(),
4081
0
                       internal::toString(-ry).c_str(),
4082
0
                       internal::toString(-rz).c_str(),
4083
0
                       internal::toString(scale_difference).c_str());
4084
0
            }
4085
0
            rx = -rx;
4086
0
            ry = -ry;
4087
0
            rz = -rz;
4088
0
            return true;
4089
0
        }
4090
0
    }
4091
20
    return false;
4092
20
}
4093
4094
//! @endcond
4095
4096
// ---------------------------------------------------------------------------
4097
4098
//! @cond Doxygen_Suppress
4099
struct AuthorityFactory::Private {
4100
    Private(const DatabaseContextNNPtr &contextIn,
4101
            const std::string &authorityName)
4102
31.4k
        : context_(contextIn), authority_(authorityName) {}
4103
4104
87.1k
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
4105
4106
102k
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
4107
102k
        return context_;
4108
102k
    }
4109
4110
    // cppcheck-suppress functionStatic
4111
31.4k
    void setThis(AuthorityFactoryNNPtr factory) {
4112
31.4k
        thisFactory_ = factory.as_nullable();
4113
31.4k
    }
4114
4115
    // cppcheck-suppress functionStatic
4116
80
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
4117
4118
37.7k
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
4119
37.7k
        if (auth_name == authority_) {
4120
25.4k
            return NN_NO_CHECK(thisFactory_.lock());
4121
25.4k
        }
4122
12.3k
        return AuthorityFactory::create(context_, auth_name);
4123
37.7k
    }
4124
4125
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
4126
                                  bool considerKnownGridsAsAvailable);
4127
4128
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
4129
                                      const std::string &code);
4130
4131
    util::PropertyMap
4132
    createProperties(const std::string &code, const std::string &name,
4133
                     bool deprecated,
4134
                     const std::vector<ObjectDomainNNPtr> &usages);
4135
4136
    util::PropertyMap
4137
    createPropertiesSearchUsages(const std::string &table_name,
4138
                                 const std::string &code,
4139
                                 const std::string &name, bool deprecated);
4140
4141
    util::PropertyMap createPropertiesSearchUsages(
4142
        const std::string &table_name, const std::string &code,
4143
        const std::string &name, bool deprecated, const std::string &remarks);
4144
4145
    SQLResultSet run(const std::string &sql,
4146
                     const ListOfParams &parameters = ListOfParams());
4147
4148
    SQLResultSet runWithCodeParam(const std::string &sql,
4149
                                  const std::string &code);
4150
4151
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
4152
4153
102k
    bool hasAuthorityRestriction() const {
4154
102k
        return !authority_.empty() && authority_ != "any";
4155
102k
    }
4156
4157
    SQLResultSet createProjectedCRSBegin(const std::string &code);
4158
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
4159
                                                 const SQLResultSet &res);
4160
4161
    SQLResultSet createDerivedProjectedCRSBegin(const std::string &code);
4162
    crs::DerivedProjectedCRSNNPtr
4163
    createDerivedProjectedCRSEnd(const std::string &code,
4164
                                 const SQLResultSet &res);
4165
4166
  private:
4167
    DatabaseContextNNPtr context_;
4168
    std::string authority_;
4169
    std::weak_ptr<AuthorityFactory> thisFactory_{};
4170
};
4171
4172
// ---------------------------------------------------------------------------
4173
4174
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
4175
49.6k
                                            const ListOfParams &parameters) {
4176
49.6k
    return context()->getPrivate()->run(sql, parameters);
4177
49.6k
}
4178
4179
// ---------------------------------------------------------------------------
4180
4181
SQLResultSet
4182
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
4183
15.7k
                                            const std::string &code) {
4184
15.7k
    return run(sql, {authority(), code});
4185
15.7k
}
4186
4187
// ---------------------------------------------------------------------------
4188
4189
SQLResultSet
4190
AuthorityFactory::Private::runWithCodeParam(const char *sql,
4191
12.7k
                                            const std::string &code) {
4192
12.7k
    return runWithCodeParam(std::string(sql), code);
4193
12.7k
}
4194
4195
// ---------------------------------------------------------------------------
4196
4197
UnitOfMeasure
4198
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
4199
11.7k
                                               const std::string &code) {
4200
11.7k
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
4201
11.7k
}
4202
4203
// ---------------------------------------------------------------------------
4204
4205
util::PropertyMap AuthorityFactory::Private::createProperties(
4206
    const std::string &code, const std::string &name, bool deprecated,
4207
11.0k
    const std::vector<ObjectDomainNNPtr> &usages) {
4208
11.0k
    auto props = util::PropertyMap()
4209
11.0k
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
4210
11.0k
                     .set(metadata::Identifier::CODE_KEY, code)
4211
11.0k
                     .set(common::IdentifiedObject::NAME_KEY, name);
4212
11.0k
    if (deprecated) {
4213
243
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
4214
243
    }
4215
11.0k
    if (!usages.empty()) {
4216
4217
10.4k
        auto array(util::ArrayOfBaseObject::create());
4218
10.4k
        for (const auto &usage : usages) {
4219
10.4k
            array->add(usage);
4220
10.4k
        }
4221
10.4k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
4222
10.4k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
4223
10.4k
    }
4224
11.0k
    return props;
4225
11.0k
}
4226
4227
// ---------------------------------------------------------------------------
4228
4229
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4230
    const std::string &table_name, const std::string &code,
4231
10.8k
    const std::string &name, bool deprecated) {
4232
4233
10.8k
    SQLResultSet res;
4234
10.8k
    if (table_name == "geodetic_crs" && code == "4326" &&
4235
6
        authority() == "EPSG") {
4236
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
4237
        // description is super verbose.
4238
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
4239
        // To avoid churn in our WKT2 output, hot patch to the usage of
4240
        // 10.076 and earlier
4241
6
        res = run("SELECT extent.description, extent.south_lat, "
4242
6
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
4243
6
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
4244
6
                  "extent.code = 1262 and scope.code = 1183");
4245
10.8k
    } else {
4246
10.8k
        const std::string sql(
4247
10.8k
            "SELECT extent.description, extent.south_lat, "
4248
10.8k
            "extent.north_lat, extent.west_lon, extent.east_lon, "
4249
10.8k
            "scope.scope, "
4250
10.8k
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
4251
10.8k
            "AS score "
4252
10.8k
            "FROM usage "
4253
10.8k
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
4254
10.8k
            "usage.extent_code = extent.code "
4255
10.8k
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
4256
10.8k
            "usage.scope_code = scope.code "
4257
10.8k
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
4258
10.8k
            "object_code = ? AND "
4259
            // We voluntary exclude extent and scope with a specific code
4260
10.8k
            "NOT (usage.extent_auth_name = 'PROJ' AND "
4261
10.8k
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
4262
10.8k
            "NOT (usage.scope_auth_name = 'PROJ' AND "
4263
10.8k
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4264
10.8k
            "ORDER BY score, usage.auth_name, usage.code");
4265
10.8k
        res = run(sql, {table_name, authority(), code});
4266
10.8k
    }
4267
10.8k
    std::vector<ObjectDomainNNPtr> usages;
4268
10.8k
    for (const auto &row : res) {
4269
10.4k
        try {
4270
10.4k
            size_t idx = 0;
4271
10.4k
            const auto &extent_description = row[idx++];
4272
10.4k
            const auto &south_lat_str = row[idx++];
4273
10.4k
            const auto &north_lat_str = row[idx++];
4274
10.4k
            const auto &west_lon_str = row[idx++];
4275
10.4k
            const auto &east_lon_str = row[idx++];
4276
10.4k
            const auto &scope = row[idx];
4277
4278
10.4k
            util::optional<std::string> scopeOpt;
4279
10.4k
            if (!scope.empty()) {
4280
10.4k
                scopeOpt = scope;
4281
10.4k
            }
4282
4283
10.4k
            metadata::ExtentPtr extent;
4284
10.4k
            if (south_lat_str.empty()) {
4285
0
                extent = metadata::Extent::create(
4286
0
                             util::optional<std::string>(extent_description),
4287
0
                             {}, {}, {})
4288
0
                             .as_nullable();
4289
10.4k
            } else {
4290
10.4k
                double south_lat = c_locale_stod(south_lat_str);
4291
10.4k
                double north_lat = c_locale_stod(north_lat_str);
4292
10.4k
                double west_lon = c_locale_stod(west_lon_str);
4293
10.4k
                double east_lon = c_locale_stod(east_lon_str);
4294
10.4k
                auto bbox = metadata::GeographicBoundingBox::create(
4295
10.4k
                    west_lon, south_lat, east_lon, north_lat);
4296
10.4k
                extent = metadata::Extent::create(
4297
10.4k
                             util::optional<std::string>(extent_description),
4298
10.4k
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4299
10.4k
                             std::vector<metadata::VerticalExtentNNPtr>(),
4300
10.4k
                             std::vector<metadata::TemporalExtentNNPtr>())
4301
10.4k
                             .as_nullable();
4302
10.4k
            }
4303
4304
10.4k
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4305
10.4k
        } catch (const std::exception &) {
4306
0
        }
4307
10.4k
    }
4308
10.8k
    return createProperties(code, name, deprecated, std::move(usages));
4309
10.8k
}
4310
4311
// ---------------------------------------------------------------------------
4312
4313
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4314
    const std::string &table_name, const std::string &code,
4315
3.94k
    const std::string &name, bool deprecated, const std::string &remarks) {
4316
3.94k
    auto props =
4317
3.94k
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4318
3.94k
    if (!remarks.empty())
4319
2.67k
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4320
3.94k
    return props;
4321
3.94k
}
4322
4323
// ---------------------------------------------------------------------------
4324
4325
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4326
    const operation::CoordinateOperationNNPtr &op,
4327
0
    bool considerKnownGridsAsAvailable) {
4328
4329
0
    struct DisableNetwork {
4330
0
        const DatabaseContextNNPtr &m_dbContext;
4331
0
        bool m_old_network_enabled = false;
4332
4333
0
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4334
0
            : m_dbContext(l_context) {
4335
0
            auto ctxt = m_dbContext->d->pjCtxt();
4336
0
            if (ctxt == nullptr) {
4337
0
                ctxt = pj_get_default_ctx();
4338
0
                m_dbContext->d->setPjCtxt(ctxt);
4339
0
            }
4340
0
            m_old_network_enabled =
4341
0
                proj_context_is_network_enabled(ctxt) != FALSE;
4342
0
            if (m_old_network_enabled)
4343
0
                proj_context_set_enable_network(ctxt, false);
4344
0
        }
4345
4346
0
        ~DisableNetwork() {
4347
0
            if (m_old_network_enabled) {
4348
0
                auto ctxt = m_dbContext->d->pjCtxt();
4349
0
                proj_context_set_enable_network(ctxt, true);
4350
0
            }
4351
0
        }
4352
0
    };
4353
4354
0
    auto &l_context = context();
4355
    // Temporarily disable networking as we are only interested in known grids
4356
0
    DisableNetwork disabler(l_context);
4357
4358
0
    for (const auto &gridDesc :
4359
0
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4360
0
        if (!gridDesc.available) {
4361
0
            return true;
4362
0
        }
4363
0
    }
4364
0
    return false;
4365
0
}
4366
4367
//! @endcond
4368
4369
// ---------------------------------------------------------------------------
4370
4371
//! @cond Doxygen_Suppress
4372
31.4k
AuthorityFactory::~AuthorityFactory() = default;
4373
//! @endcond
4374
4375
// ---------------------------------------------------------------------------
4376
4377
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4378
                                   const std::string &authorityName)
4379
31.4k
    : d(std::make_unique<Private>(context, authorityName)) {}
4380
4381
// ---------------------------------------------------------------------------
4382
4383
// clang-format off
4384
/** \brief Instantiate a AuthorityFactory.
4385
 *
4386
 * The authority name might be set to the empty string in the particular case
4387
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4388
 * is called.
4389
 *
4390
 * @param context Context.
4391
 * @param authorityName Authority name.
4392
 * @return new AuthorityFactory.
4393
 */
4394
// clang-format on
4395
4396
AuthorityFactoryNNPtr
4397
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4398
31.4k
                         const std::string &authorityName) {
4399
31.4k
    const auto getFactory = [&context, &authorityName]() {
4400
31.4k
        for (const auto &knownName :
4401
68.9k
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4402
68.9k
            if (ci_equal(authorityName, knownName)) {
4403
13.6k
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4404
13.6k
                    context, knownName);
4405
13.6k
            }
4406
68.9k
        }
4407
17.8k
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4408
17.8k
            context, authorityName);
4409
31.4k
    };
4410
31.4k
    auto factory = getFactory();
4411
31.4k
    factory->d->setThis(factory);
4412
31.4k
    return factory;
4413
31.4k
}
4414
4415
// ---------------------------------------------------------------------------
4416
4417
/** \brief Returns the database context. */
4418
652
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4419
652
    return d->context();
4420
652
}
4421
4422
// ---------------------------------------------------------------------------
4423
4424
//! @cond Doxygen_Suppress
4425
AuthorityFactory::CRSInfo::CRSInfo()
4426
0
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4427
0
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4428
0
      north_lat_degree{}, areaName{}, projectionMethodName{},
4429
0
      celestialBodyName{} {}
4430
//! @endcond
4431
4432
// ---------------------------------------------------------------------------
4433
4434
/** \brief Returns an arbitrary object from a code.
4435
 *
4436
 * The returned object will typically be an instance of Datum,
4437
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4438
 * the object is know at compile time, it is recommended to invoke the most
4439
 * precise method instead of this one (for example
4440
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4441
 * if the caller know he is asking for a coordinate reference system).
4442
 *
4443
 * If there are several objects with the same code, a FactoryException is
4444
 * thrown.
4445
 *
4446
 * @param code Object code allocated by authority. (e.g. "4326")
4447
 * @return object.
4448
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4449
 * @throw FactoryException in case of other errors.
4450
 */
4451
4452
util::BaseObjectNNPtr
4453
0
AuthorityFactory::createObject(const std::string &code) const {
4454
4455
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4456
0
                                   "WHERE auth_name = ? AND code = ?",
4457
0
                                   code);
4458
0
    if (res.empty()) {
4459
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4460
0
    }
4461
0
    if (res.size() != 1) {
4462
0
        std::string msg(
4463
0
            "More than one object matching specified code. Objects found in ");
4464
0
        bool first = true;
4465
0
        for (const auto &row : res) {
4466
0
            if (!first)
4467
0
                msg += ", ";
4468
0
            msg += row[0];
4469
0
            first = false;
4470
0
        }
4471
0
        throw FactoryException(msg);
4472
0
    }
4473
0
    const auto &first_row = res.front();
4474
0
    const auto &table_name = first_row[0];
4475
0
    const auto &type = first_row[1];
4476
0
    if (table_name == "extent") {
4477
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4478
0
            createExtent(code));
4479
0
    }
4480
0
    if (table_name == "unit_of_measure") {
4481
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4482
0
            createUnitOfMeasure(code));
4483
0
    }
4484
0
    if (table_name == "prime_meridian") {
4485
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4486
0
            createPrimeMeridian(code));
4487
0
    }
4488
0
    if (table_name == "ellipsoid") {
4489
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4490
0
            createEllipsoid(code));
4491
0
    }
4492
0
    if (table_name == "geodetic_datum") {
4493
0
        if (type == "ensemble") {
4494
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4495
0
                createDatumEnsemble(code, table_name));
4496
0
        }
4497
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4498
0
            createGeodeticDatum(code));
4499
0
    }
4500
0
    if (table_name == "vertical_datum") {
4501
0
        if (type == "ensemble") {
4502
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4503
0
                createDatumEnsemble(code, table_name));
4504
0
        }
4505
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4506
0
            createVerticalDatum(code));
4507
0
    }
4508
0
    if (table_name == "engineering_datum") {
4509
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4510
0
            createEngineeringDatum(code));
4511
0
    }
4512
0
    if (table_name == "geodetic_crs") {
4513
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4514
0
            createGeodeticCRS(code));
4515
0
    }
4516
0
    if (table_name == "vertical_crs") {
4517
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4518
0
            createVerticalCRS(code));
4519
0
    }
4520
0
    if (table_name == "projected_crs") {
4521
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4522
0
            createProjectedCRS(code));
4523
0
    }
4524
0
    if (table_name == "derived_projected_crs") {
4525
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4526
0
            createDerivedProjectedCRS(code));
4527
0
    }
4528
0
    if (table_name == "compound_crs") {
4529
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4530
0
            createCompoundCRS(code));
4531
0
    }
4532
0
    if (table_name == "engineering_crs") {
4533
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4534
0
            createEngineeringCRS(code));
4535
0
    }
4536
0
    if (table_name == "conversion") {
4537
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4538
0
            createConversion(code));
4539
0
    }
4540
0
    if (table_name == "helmert_transformation" ||
4541
0
        table_name == "grid_transformation" ||
4542
0
        table_name == "other_transformation" ||
4543
0
        table_name == "concatenated_operation") {
4544
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4545
0
            createCoordinateOperation(code, false));
4546
0
    }
4547
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4548
0
}
4549
4550
// ---------------------------------------------------------------------------
4551
4552
//! @cond Doxygen_Suppress
4553
static FactoryException buildFactoryException(const char *type,
4554
                                              const std::string &authName,
4555
                                              const std::string &code,
4556
0
                                              const std::exception &ex) {
4557
0
    return FactoryException(std::string("cannot build ") + type + " " +
4558
0
                            authName + ":" + code + ": " + ex.what());
4559
0
}
4560
//! @endcond
4561
4562
// ---------------------------------------------------------------------------
4563
4564
/** \brief Returns a metadata::Extent from the specified code.
4565
 *
4566
 * @param code Object code allocated by authority.
4567
 * @return object.
4568
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4569
 * @throw FactoryException in case of other errors.
4570
 */
4571
4572
metadata::ExtentNNPtr
4573
0
AuthorityFactory::createExtent(const std::string &code) const {
4574
0
    const auto cacheKey(d->authority() + code);
4575
0
    {
4576
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4577
0
        if (extent) {
4578
0
            return NN_NO_CHECK(extent);
4579
0
        }
4580
0
    }
4581
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4582
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4583
0
    auto res = d->runWithCodeParam(sql, code);
4584
0
    if (res.empty()) {
4585
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4586
0
                                           code);
4587
0
    }
4588
0
    try {
4589
0
        const auto &row = res.front();
4590
0
        const auto &description = row[0];
4591
0
        if (row[1].empty()) {
4592
0
            auto extent = metadata::Extent::create(
4593
0
                util::optional<std::string>(description), {}, {}, {});
4594
0
            d->context()->d->cache(cacheKey, extent);
4595
0
            return extent;
4596
0
        }
4597
0
        double south_lat = c_locale_stod(row[1]);
4598
0
        double north_lat = c_locale_stod(row[2]);
4599
0
        double west_lon = c_locale_stod(row[3]);
4600
0
        double east_lon = c_locale_stod(row[4]);
4601
0
        auto bbox = metadata::GeographicBoundingBox::create(
4602
0
            west_lon, south_lat, east_lon, north_lat);
4603
4604
0
        auto extent = metadata::Extent::create(
4605
0
            util::optional<std::string>(description),
4606
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4607
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4608
0
            std::vector<metadata::TemporalExtentNNPtr>());
4609
0
        d->context()->d->cache(cacheKey, extent);
4610
0
        return extent;
4611
4612
0
    } catch (const std::exception &ex) {
4613
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4614
0
    }
4615
0
}
4616
4617
// ---------------------------------------------------------------------------
4618
4619
/** \brief Returns a common::UnitOfMeasure from the specified code.
4620
 *
4621
 * @param code Object code allocated by authority.
4622
 * @return object.
4623
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4624
 * @throw FactoryException in case of other errors.
4625
 */
4626
4627
UnitOfMeasureNNPtr
4628
11.7k
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4629
11.7k
    const auto cacheKey(d->authority() + code);
4630
11.7k
    {
4631
11.7k
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4632
11.7k
        if (uom) {
4633
11.6k
            return NN_NO_CHECK(uom);
4634
11.6k
        }
4635
11.7k
    }
4636
123
    auto res = d->context()->d->run(
4637
123
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4638
123
        "auth_name = ? AND code = ?",
4639
123
        {d->authority(), code}, true);
4640
123
    if (res.empty()) {
4641
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4642
0
                                           d->authority(), code);
4643
0
    }
4644
123
    try {
4645
123
        const auto &row = res.front();
4646
123
        const auto &name =
4647
123
            (row[0] == "degree (supplier to define representation)")
4648
123
                ? UnitOfMeasure::DEGREE.name()
4649
123
                : row[0];
4650
123
        double conv_factor = (code == "9107" || code == "9108")
4651
123
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4652
123
                                 : c_locale_stod(row[1]);
4653
123
        constexpr double EPS = 1e-10;
4654
123
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4655
123
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4656
52
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4657
52
        }
4658
123
        if (std::fabs(conv_factor -
4659
123
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4660
123
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4661
1
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4662
1
        }
4663
123
        const auto &type_str = row[2];
4664
123
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4665
123
        if (type_str == "length")
4666
45
            unitType = UnitOfMeasure::Type::LINEAR;
4667
78
        else if (type_str == "angle")
4668
58
            unitType = UnitOfMeasure::Type::ANGULAR;
4669
20
        else if (type_str == "scale")
4670
19
            unitType = UnitOfMeasure::Type::SCALE;
4671
1
        else if (type_str == "time")
4672
1
            unitType = UnitOfMeasure::Type::TIME;
4673
123
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4674
123
            name, conv_factor, unitType, d->authority(), code);
4675
123
        d->context()->d->cache(cacheKey, uom);
4676
123
        return uom;
4677
123
    } catch (const std::exception &ex) {
4678
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4679
0
                                    ex);
4680
0
    }
4681
123
}
4682
4683
// ---------------------------------------------------------------------------
4684
4685
//! @cond Doxygen_Suppress
4686
static double normalizeMeasure(const std::string &uom_code,
4687
                               const std::string &value,
4688
9.54k
                               std::string &normalized_uom_code) {
4689
9.54k
    if (uom_code == "9110") // DDD.MMSSsss.....
4690
1.89k
    {
4691
1.89k
        double normalized_value = c_locale_stod(value);
4692
1.89k
        std::ostringstream buffer;
4693
1.89k
        buffer.imbue(std::locale::classic());
4694
1.89k
        constexpr size_t precision = 12;
4695
1.89k
        buffer << std::fixed << std::setprecision(precision)
4696
1.89k
               << normalized_value;
4697
1.89k
        auto formatted = buffer.str();
4698
1.89k
        size_t dotPos = formatted.find('.');
4699
1.89k
        assert(dotPos + 1 + precision == formatted.size());
4700
1.89k
        auto minutes = formatted.substr(dotPos + 1, 2);
4701
1.89k
        auto seconds = formatted.substr(dotPos + 3);
4702
1.89k
        assert(seconds.size() == precision - 2);
4703
1.89k
        normalized_value =
4704
1.89k
            (normalized_value < 0 ? -1.0 : 1.0) *
4705
1.89k
            (std::floor(std::fabs(normalized_value)) +
4706
1.89k
             c_locale_stod(minutes) / 60. +
4707
1.89k
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4708
1.89k
                 3600.);
4709
1.89k
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4710
        /* coverity[overflow_sink] */
4711
1.89k
        return normalized_value;
4712
7.64k
    } else {
4713
7.64k
        normalized_uom_code = uom_code;
4714
7.64k
        return c_locale_stod(value);
4715
7.64k
    }
4716
9.54k
}
4717
//! @endcond
4718
4719
// ---------------------------------------------------------------------------
4720
4721
/** \brief Returns a datum::PrimeMeridian from the specified code.
4722
 *
4723
 * @param code Object code allocated by authority.
4724
 * @return object.
4725
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4726
 * @throw FactoryException in case of other errors.
4727
 */
4728
4729
datum::PrimeMeridianNNPtr
4730
1.56k
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4731
1.56k
    const auto cacheKey(d->authority() + code);
4732
1.56k
    {
4733
1.56k
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4734
1.56k
        if (pm) {
4735
1.51k
            return NN_NO_CHECK(pm);
4736
1.51k
        }
4737
1.56k
    }
4738
53
    auto res = d->runWithCodeParam(
4739
53
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4740
53
        "prime_meridian WHERE "
4741
53
        "auth_name = ? AND code = ?",
4742
53
        code);
4743
53
    if (res.empty()) {
4744
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4745
0
                                           d->authority(), code);
4746
0
    }
4747
53
    try {
4748
53
        const auto &row = res.front();
4749
53
        const auto &name = row[0];
4750
53
        const auto &longitude = row[1];
4751
53
        const auto &uom_auth_name = row[2];
4752
53
        const auto &uom_code = row[3];
4753
53
        const bool deprecated = row[4] == "1";
4754
4755
53
        std::string normalized_uom_code(uom_code);
4756
53
        const double normalized_value =
4757
53
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4758
4759
53
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4760
53
        auto props = d->createProperties(code, name, deprecated, {});
4761
53
        auto pm = datum::PrimeMeridian::create(
4762
53
            props, common::Angle(normalized_value, uom));
4763
53
        d->context()->d->cache(cacheKey, pm);
4764
53
        return pm;
4765
53
    } catch (const std::exception &ex) {
4766
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4767
0
    }
4768
53
}
4769
4770
// ---------------------------------------------------------------------------
4771
4772
/** \brief Identify a celestial body from an approximate radius.
4773
 *
4774
 * @param semi_major_axis Approximate semi-major axis.
4775
 * @param tolerance Relative error allowed.
4776
 * @return celestial body name if one single match found.
4777
 * @throw FactoryException in case of error.
4778
 */
4779
4780
std::string
4781
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4782
7.08k
                                                double tolerance) const {
4783
7.08k
    auto res =
4784
7.08k
        d->run("SELECT DISTINCT name, "
4785
7.08k
               "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error "
4786
7.08k
               "FROM celestial_body WHERE rel_error <= ? "
4787
7.08k
               "ORDER BY rel_error, name",
4788
7.08k
               {semi_major_axis, tolerance});
4789
7.08k
    if (res.empty()) {
4790
6.90k
        throw FactoryException("no match found");
4791
6.90k
    }
4792
180
    constexpr int IDX_NAME = 0;
4793
180
    if (res.size() > 1) {
4794
33
        constexpr int IDX_REL_ERROR = 1;
4795
        // If the first object has a relative error of 0 and the next one
4796
        // a non-zero error, then use the first one.
4797
33
        if (res.front()[IDX_REL_ERROR] == "0" &&
4798
0
            (*std::next(res.begin()))[IDX_REL_ERROR] != "0") {
4799
0
            return res.front()[IDX_NAME];
4800
0
        }
4801
66
        for (const auto &row : res) {
4802
66
            if (row[IDX_NAME] != res.front()[IDX_NAME]) {
4803
31
                throw FactoryException("more than one match found");
4804
31
            }
4805
66
        }
4806
33
    }
4807
149
    return res.front()[IDX_NAME];
4808
180
}
4809
4810
// ---------------------------------------------------------------------------
4811
4812
/** \brief Returns a datum::Ellipsoid from the specified code.
4813
 *
4814
 * @param code Object code allocated by authority.
4815
 * @return object.
4816
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4817
 * @throw FactoryException in case of other errors.
4818
 */
4819
4820
datum::EllipsoidNNPtr
4821
1.69k
AuthorityFactory::createEllipsoid(const std::string &code) const {
4822
1.69k
    const auto cacheKey(d->authority() + code);
4823
1.69k
    {
4824
1.69k
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4825
1.69k
        if (ellps) {
4826
1.55k
            return NN_NO_CHECK(ellps);
4827
1.55k
        }
4828
1.69k
    }
4829
142
    auto res = d->runWithCodeParam(
4830
142
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4831
142
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4832
142
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4833
142
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4834
142
        "ellipsoid JOIN celestial_body "
4835
142
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4836
142
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4837
142
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4838
142
        code);
4839
142
    if (res.empty()) {
4840
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4841
0
                                           d->authority(), code);
4842
0
    }
4843
142
    try {
4844
142
        const auto &row = res.front();
4845
142
        const auto &name = row[0];
4846
142
        const auto &semi_major_axis_str = row[1];
4847
142
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4848
142
        const auto &uom_auth_name = row[2];
4849
142
        const auto &uom_code = row[3];
4850
142
        const auto &inv_flattening_str = row[4];
4851
142
        const auto &semi_minor_axis_str = row[5];
4852
142
        const auto &body = row[6];
4853
142
        const bool deprecated = row[7] == "1";
4854
142
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4855
142
        auto props = d->createProperties(code, name, deprecated, {});
4856
142
        if (!inv_flattening_str.empty()) {
4857
107
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4858
107
                props, common::Length(semi_major_axis, uom),
4859
107
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4860
107
            d->context()->d->cache(cacheKey, ellps);
4861
107
            return ellps;
4862
107
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4863
20
            auto ellps = datum::Ellipsoid::createSphere(
4864
20
                props, common::Length(semi_major_axis, uom), body);
4865
20
            d->context()->d->cache(cacheKey, ellps);
4866
20
            return ellps;
4867
20
        } else {
4868
15
            auto ellps = datum::Ellipsoid::createTwoAxis(
4869
15
                props, common::Length(semi_major_axis, uom),
4870
15
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4871
15
            d->context()->d->cache(cacheKey, ellps);
4872
15
            return ellps;
4873
15
        }
4874
142
    } catch (const std::exception &ex) {
4875
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4876
0
    }
4877
142
}
4878
4879
// ---------------------------------------------------------------------------
4880
4881
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4882
 *
4883
 * @param code Object code allocated by authority.
4884
 * @return object.
4885
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4886
 * @throw FactoryException in case of other errors.
4887
 */
4888
4889
datum::GeodeticReferenceFrameNNPtr
4890
1.03k
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4891
4892
1.03k
    datum::GeodeticReferenceFramePtr datum;
4893
1.03k
    datum::DatumEnsemblePtr datumEnsemble;
4894
1.03k
    constexpr bool turnEnsembleAsDatum = true;
4895
1.03k
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4896
1.03k
                                  turnEnsembleAsDatum);
4897
1.03k
    return NN_NO_CHECK(datum);
4898
1.03k
}
4899
4900
// ---------------------------------------------------------------------------
4901
4902
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4903
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4904
3.93k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4905
3.93k
    const auto cacheKey(d->authority() + code);
4906
3.93k
    {
4907
3.93k
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4908
3.93k
        if (outDatumEnsemble) {
4909
314
            if (!turnEnsembleAsDatum)
4910
113
                return;
4911
201
            outDatumEnsemble = nullptr;
4912
201
        }
4913
3.82k
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4914
3.82k
        if (outDatum) {
4915
2.24k
            return;
4916
2.24k
        }
4917
3.82k
    }
4918
1.57k
    auto res = d->runWithCodeParam(
4919
1.57k
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4920
1.57k
        "prime_meridian_auth_name, prime_meridian_code, "
4921
1.57k
        "publication_date, frame_reference_epoch, "
4922
1.57k
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4923
1.57k
        "FROM geodetic_datum "
4924
1.57k
        "WHERE "
4925
1.57k
        "auth_name = ? AND code = ?",
4926
1.57k
        code);
4927
1.57k
    if (res.empty()) {
4928
0
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4929
0
                                           d->authority(), code);
4930
0
    }
4931
1.57k
    try {
4932
1.57k
        const auto &row = res.front();
4933
1.57k
        const auto &name = row[0];
4934
1.57k
        const auto &ellipsoid_auth_name = row[1];
4935
1.57k
        const auto &ellipsoid_code = row[2];
4936
1.57k
        const auto &prime_meridian_auth_name = row[3];
4937
1.57k
        const auto &prime_meridian_code = row[4];
4938
1.57k
        const auto &publication_date = row[5];
4939
1.57k
        const auto &frame_reference_epoch = row[6];
4940
1.57k
        const auto &ensemble_accuracy = row[7];
4941
1.57k
        const auto &anchor = row[8];
4942
1.57k
        const auto &anchor_epoch = row[9];
4943
1.57k
        const bool deprecated = row[10] == "1";
4944
4945
1.57k
        std::string massagedName;
4946
1.57k
        if (turnEnsembleAsDatum) {
4947
414
            massagedName =
4948
414
                datum::DatumEnsemble::ensembleNameToNonEnsembleName(name);
4949
414
        }
4950
1.57k
        if (massagedName.empty()) {
4951
1.56k
            massagedName = name;
4952
1.56k
        }
4953
1.57k
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4954
1.57k
                                                     massagedName, deprecated);
4955
4956
1.57k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4957
8
            auto resMembers =
4958
8
                d->run("SELECT member_auth_name, member_code FROM "
4959
8
                       "geodetic_datum_ensemble_member WHERE "
4960
8
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4961
8
                       "ORDER BY sequence",
4962
8
                       {d->authority(), code});
4963
4964
8
            std::vector<datum::DatumNNPtr> members;
4965
364
            for (const auto &memberRow : resMembers) {
4966
364
                members.push_back(
4967
364
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4968
364
            }
4969
8
            auto datumEnsemble = datum::DatumEnsemble::create(
4970
8
                props, std::move(members),
4971
8
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4972
8
            d->context()->d->cache(cacheKey, datumEnsemble);
4973
8
            outDatumEnsemble = datumEnsemble.as_nullable();
4974
1.56k
        } else {
4975
1.56k
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4976
1.56k
                                 ->createEllipsoid(ellipsoid_code);
4977
1.56k
            auto pm = d->createFactory(prime_meridian_auth_name)
4978
1.56k
                          ->createPrimeMeridian(prime_meridian_code);
4979
4980
1.56k
            auto anchorOpt = util::optional<std::string>();
4981
1.56k
            if (!anchor.empty())
4982
17
                anchorOpt = anchor;
4983
1.56k
            if (!publication_date.empty()) {
4984
1.17k
                props.set("PUBLICATION_DATE", publication_date);
4985
1.17k
            }
4986
1.56k
            if (!anchor_epoch.empty()) {
4987
373
                props.set("ANCHOR_EPOCH", anchor_epoch);
4988
373
            }
4989
1.56k
            auto datum = frame_reference_epoch.empty()
4990
1.56k
                             ? datum::GeodeticReferenceFrame::create(
4991
1.32k
                                   props, ellipsoid, anchorOpt, pm)
4992
1.56k
                             : util::nn_static_pointer_cast<
4993
243
                                   datum::GeodeticReferenceFrame>(
4994
243
                                   datum::DynamicGeodeticReferenceFrame::create(
4995
243
                                       props, ellipsoid, anchorOpt, pm,
4996
243
                                       common::Measure(
4997
243
                                           c_locale_stod(frame_reference_epoch),
4998
243
                                           common::UnitOfMeasure::YEAR),
4999
243
                                       util::optional<std::string>()));
5000
1.56k
            d->context()->d->cache(cacheKey, datum);
5001
1.56k
            outDatum = datum.as_nullable();
5002
1.56k
        }
5003
1.57k
    } catch (const std::exception &ex) {
5004
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
5005
0
                                    code, ex);
5006
0
    }
5007
1.57k
}
5008
5009
// ---------------------------------------------------------------------------
5010
5011
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
5012
 *
5013
 * @param code Object code allocated by authority.
5014
 * @return object.
5015
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5016
 * @throw FactoryException in case of other errors.
5017
 */
5018
5019
datum::VerticalReferenceFrameNNPtr
5020
96
AuthorityFactory::createVerticalDatum(const std::string &code) const {
5021
96
    datum::VerticalReferenceFramePtr datum;
5022
96
    datum::DatumEnsemblePtr datumEnsemble;
5023
96
    constexpr bool turnEnsembleAsDatum = true;
5024
96
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
5025
96
                                  turnEnsembleAsDatum);
5026
96
    return NN_NO_CHECK(datum);
5027
96
}
5028
5029
// ---------------------------------------------------------------------------
5030
5031
void AuthorityFactory::createVerticalDatumOrEnsemble(
5032
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
5033
711
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
5034
711
    auto res =
5035
711
        d->runWithCodeParam("SELECT name, publication_date, "
5036
711
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
5037
711
                            "anchor_epoch, deprecated FROM "
5038
711
                            "vertical_datum WHERE auth_name = ? AND code = ?",
5039
711
                            code);
5040
711
    if (res.empty()) {
5041
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
5042
0
                                           d->authority(), code);
5043
0
    }
5044
711
    try {
5045
711
        const auto &row = res.front();
5046
711
        const auto &name = row[0];
5047
711
        const auto &publication_date = row[1];
5048
711
        const auto &frame_reference_epoch = row[2];
5049
711
        const auto &ensemble_accuracy = row[3];
5050
711
        const auto &anchor = row[4];
5051
711
        const auto &anchor_epoch = row[5];
5052
711
        const bool deprecated = row[6] == "1";
5053
711
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
5054
711
                                                     name, deprecated);
5055
711
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
5056
22
            auto resMembers =
5057
22
                d->run("SELECT member_auth_name, member_code FROM "
5058
22
                       "vertical_datum_ensemble_member WHERE "
5059
22
                       "ensemble_auth_name = ? AND ensemble_code = ? "
5060
22
                       "ORDER BY sequence",
5061
22
                       {d->authority(), code});
5062
5063
22
            std::vector<datum::DatumNNPtr> members;
5064
96
            for (const auto &memberRow : resMembers) {
5065
96
                members.push_back(
5066
96
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5067
96
            }
5068
22
            auto datumEnsemble = datum::DatumEnsemble::create(
5069
22
                props, std::move(members),
5070
22
                metadata::PositionalAccuracy::create(ensemble_accuracy));
5071
22
            outDatumEnsemble = datumEnsemble.as_nullable();
5072
689
        } else {
5073
689
            if (!publication_date.empty()) {
5074
371
                props.set("PUBLICATION_DATE", publication_date);
5075
371
            }
5076
689
            if (!anchor_epoch.empty()) {
5077
12
                props.set("ANCHOR_EPOCH", anchor_epoch);
5078
12
            }
5079
689
            if (d->authority() == "ESRI" &&
5080
206
                starts_with(code, "from_geogdatum_")) {
5081
201
                props.set("VERT_DATUM_TYPE", "2002");
5082
201
            }
5083
689
            auto anchorOpt = util::optional<std::string>();
5084
689
            if (!anchor.empty())
5085
0
                anchorOpt = anchor;
5086
689
            if (frame_reference_epoch.empty()) {
5087
684
                outDatum =
5088
684
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
5089
684
                        .as_nullable();
5090
684
            } else {
5091
5
                outDatum =
5092
5
                    datum::DynamicVerticalReferenceFrame::create(
5093
5
                        props, anchorOpt,
5094
5
                        util::optional<datum::RealizationMethod>(),
5095
5
                        common::Measure(c_locale_stod(frame_reference_epoch),
5096
5
                                        common::UnitOfMeasure::YEAR),
5097
5
                        util::optional<std::string>())
5098
5
                        .as_nullable();
5099
5
            }
5100
689
        }
5101
711
    } catch (const std::exception &ex) {
5102
0
        throw buildFactoryException("vertical reference frame", d->authority(),
5103
0
                                    code, ex);
5104
0
    }
5105
711
}
5106
5107
// ---------------------------------------------------------------------------
5108
5109
/** \brief Returns a datum::EngineeringDatum from the specified code.
5110
 *
5111
 * @param code Object code allocated by authority.
5112
 * @return object.
5113
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5114
 * @throw FactoryException in case of other errors.
5115
 * @since 9.6
5116
 */
5117
5118
datum::EngineeringDatumNNPtr
5119
24
AuthorityFactory::createEngineeringDatum(const std::string &code) const {
5120
24
    auto res = d->runWithCodeParam(
5121
24
        "SELECT name, publication_date, "
5122
24
        "anchor, anchor_epoch, deprecated FROM "
5123
24
        "engineering_datum WHERE auth_name = ? AND code = ?",
5124
24
        code);
5125
24
    if (res.empty()) {
5126
0
        throw NoSuchAuthorityCodeException("engineering datum not found",
5127
0
                                           d->authority(), code);
5128
0
    }
5129
24
    try {
5130
24
        const auto &row = res.front();
5131
24
        const auto &name = row[0];
5132
24
        const auto &publication_date = row[1];
5133
24
        const auto &anchor = row[2];
5134
24
        const auto &anchor_epoch = row[3];
5135
24
        const bool deprecated = row[4] == "1";
5136
24
        auto props = d->createPropertiesSearchUsages("engineering_datum", code,
5137
24
                                                     name, deprecated);
5138
5139
24
        if (!publication_date.empty()) {
5140
3
            props.set("PUBLICATION_DATE", publication_date);
5141
3
        }
5142
24
        if (!anchor_epoch.empty()) {
5143
0
            props.set("ANCHOR_EPOCH", anchor_epoch);
5144
0
        }
5145
24
        auto anchorOpt = util::optional<std::string>();
5146
24
        if (!anchor.empty())
5147
0
            anchorOpt = anchor;
5148
24
        return datum::EngineeringDatum::create(props, anchorOpt);
5149
24
    } catch (const std::exception &ex) {
5150
0
        throw buildFactoryException("engineering datum", d->authority(), code,
5151
0
                                    ex);
5152
0
    }
5153
24
}
5154
5155
// ---------------------------------------------------------------------------
5156
5157
/** \brief Returns a datum::DatumEnsemble from the specified code.
5158
 *
5159
 * @param code Object code allocated by authority.
5160
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
5161
 * @return object.
5162
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5163
 * @throw FactoryException in case of other errors.
5164
 */
5165
5166
datum::DatumEnsembleNNPtr
5167
AuthorityFactory::createDatumEnsemble(const std::string &code,
5168
0
                                      const std::string &type) const {
5169
0
    auto res = d->run(
5170
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
5171
0
        "geodetic_datum WHERE "
5172
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
5173
0
        "UNION ALL "
5174
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
5175
0
        "vertical_datum WHERE "
5176
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
5177
0
        {d->authority(), code, d->authority(), code});
5178
0
    if (res.empty()) {
5179
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
5180
0
                                           d->authority(), code);
5181
0
    }
5182
0
    for (const auto &row : res) {
5183
0
        const std::string &gotType = row[0];
5184
0
        const std::string &name = row[1];
5185
0
        const std::string &ensembleAccuracy = row[2];
5186
0
        const bool deprecated = row[3] == "1";
5187
0
        if (type.empty() || type == gotType) {
5188
0
            auto resMembers =
5189
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
5190
0
                           "_ensemble_member WHERE "
5191
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
5192
0
                           "ORDER BY sequence",
5193
0
                       {d->authority(), code});
5194
5195
0
            std::vector<datum::DatumNNPtr> members;
5196
0
            for (const auto &memberRow : resMembers) {
5197
0
                members.push_back(
5198
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5199
0
            }
5200
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
5201
0
                                                         deprecated);
5202
0
            return datum::DatumEnsemble::create(
5203
0
                props, std::move(members),
5204
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
5205
0
        }
5206
0
    }
5207
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
5208
0
                                       d->authority(), code);
5209
0
}
5210
5211
// ---------------------------------------------------------------------------
5212
5213
/** \brief Returns a datum::Datum from the specified code.
5214
 *
5215
 * @param code Object code allocated by authority.
5216
 * @return object.
5217
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5218
 * @throw FactoryException in case of other errors.
5219
 */
5220
5221
460
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
5222
460
    auto res = d->run(
5223
460
        "SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
5224
460
        "auth_name = ? AND code = ? "
5225
460
        "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
5226
460
        "auth_name = ? AND code = ? "
5227
460
        "UNION ALL SELECT 'engineering_datum' FROM engineering_datum "
5228
460
        "WHERE "
5229
460
        "auth_name = ? AND code = ?",
5230
460
        {d->authority(), code, d->authority(), code, d->authority(), code});
5231
460
    if (res.empty()) {
5232
0
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
5233
0
                                           code);
5234
0
    }
5235
460
    const auto &type = res.front()[0];
5236
460
    if (type == "geodetic_datum") {
5237
364
        return createGeodeticDatum(code);
5238
364
    }
5239
96
    if (type == "vertical_datum") {
5240
96
        return createVerticalDatum(code);
5241
96
    }
5242
0
    return createEngineeringDatum(code);
5243
96
}
5244
5245
// ---------------------------------------------------------------------------
5246
5247
//! @cond Doxygen_Suppress
5248
32
static cs::MeridianPtr createMeridian(const std::string &val) {
5249
32
    try {
5250
32
        const std::string degW(std::string("\xC2\xB0") + "W");
5251
32
        if (ends_with(val, degW)) {
5252
13
            return cs::Meridian::create(common::Angle(
5253
13
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
5254
13
        }
5255
19
        const std::string degE(std::string("\xC2\xB0") + "E");
5256
19
        if (ends_with(val, degE)) {
5257
19
            return cs::Meridian::create(common::Angle(
5258
19
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
5259
19
        }
5260
19
    } catch (const std::exception &) {
5261
0
    }
5262
0
    return nullptr;
5263
32
}
5264
//! @endcond
5265
5266
// ---------------------------------------------------------------------------
5267
5268
/** \brief Returns a cs::CoordinateSystem from the specified code.
5269
 *
5270
 * @param code Object code allocated by authority.
5271
 * @return object.
5272
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5273
 * @throw FactoryException in case of other errors.
5274
 */
5275
5276
cs::CoordinateSystemNNPtr
5277
4.87k
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
5278
4.87k
    const auto cacheKey(d->authority() + code);
5279
4.87k
    {
5280
4.87k
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
5281
4.87k
        if (cs) {
5282
4.73k
            return NN_NO_CHECK(cs);
5283
4.73k
        }
5284
4.87k
    }
5285
143
    auto res = d->runWithCodeParam(
5286
143
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
5287
143
        "cs.type FROM "
5288
143
        "axis LEFT JOIN coordinate_system cs ON "
5289
143
        "axis.coordinate_system_auth_name = cs.auth_name AND "
5290
143
        "axis.coordinate_system_code = cs.code WHERE "
5291
143
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
5292
143
        "BY coordinate_system_order",
5293
143
        code);
5294
143
    if (res.empty()) {
5295
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
5296
0
                                           d->authority(), code);
5297
0
    }
5298
5299
143
    const auto &csType = res.front()[5];
5300
143
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
5301
287
    for (const auto &row : res) {
5302
287
        const auto &name = row[0];
5303
287
        const auto &abbrev = row[1];
5304
287
        const auto &orientation = row[2];
5305
287
        const auto &uom_auth_name = row[3];
5306
287
        const auto &uom_code = row[4];
5307
287
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
5308
0
            throw FactoryException("no unit of measure for an axis is only "
5309
0
                                   "supported for ordinatal CS");
5310
0
        }
5311
287
        auto uom = uom_auth_name.empty()
5312
287
                       ? common::UnitOfMeasure::NONE
5313
287
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
5314
287
        auto props =
5315
287
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
5316
287
        const cs::AxisDirection *direction =
5317
287
            cs::AxisDirection::valueOf(orientation);
5318
287
        cs::MeridianPtr meridian;
5319
287
        if (direction == nullptr) {
5320
32
            if (orientation == "Geocentre > equator/0"
5321
32
                               "\xC2\xB0"
5322
32
                               "E") {
5323
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
5324
32
            } else if (orientation == "Geocentre > equator/90"
5325
32
                                      "\xC2\xB0"
5326
32
                                      "E") {
5327
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
5328
32
            } else if (orientation == "Geocentre > north pole") {
5329
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
5330
32
            } else if (starts_with(orientation, "North along ")) {
5331
18
                direction = &(cs::AxisDirection::NORTH);
5332
18
                meridian =
5333
18
                    createMeridian(orientation.substr(strlen("North along ")));
5334
18
            } else if (starts_with(orientation, "South along ")) {
5335
14
                direction = &(cs::AxisDirection::SOUTH);
5336
14
                meridian =
5337
14
                    createMeridian(orientation.substr(strlen("South along ")));
5338
14
            } else {
5339
0
                throw FactoryException("unknown axis direction: " +
5340
0
                                       orientation);
5341
0
            }
5342
32
        }
5343
287
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5344
287
            props, abbrev, *direction, uom, meridian));
5345
287
    }
5346
5347
143
    const auto cacheAndRet = [this,
5348
143
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5349
143
        d->context()->d->cache(cacheKey, cs);
5350
143
        return cs;
5351
143
    };
5352
5353
143
    auto props = util::PropertyMap()
5354
143
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5355
143
                     .set(metadata::Identifier::CODE_KEY, code);
5356
143
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5357
49
        if (axisList.size() == 2) {
5358
34
            return cacheAndRet(
5359
34
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5360
34
        }
5361
15
        if (axisList.size() == 3) {
5362
15
            return cacheAndRet(cs::EllipsoidalCS::create(
5363
15
                props, axisList[0], axisList[1], axisList[2]));
5364
15
        }
5365
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5366
15
    }
5367
94
    if (csType == CS_TYPE_CARTESIAN) {
5368
69
        if (axisList.size() == 2) {
5369
59
            return cacheAndRet(
5370
59
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5371
59
        }
5372
10
        if (axisList.size() == 3) {
5373
10
            return cacheAndRet(cs::CartesianCS::create(
5374
10
                props, axisList[0], axisList[1], axisList[2]));
5375
10
        }
5376
0
        throw FactoryException("invalid number of axis for CartesianCS");
5377
10
    }
5378
25
    if (csType == CS_TYPE_SPHERICAL) {
5379
1
        if (axisList.size() == 2) {
5380
1
            return cacheAndRet(
5381
1
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5382
1
        }
5383
0
        if (axisList.size() == 3) {
5384
0
            return cacheAndRet(cs::SphericalCS::create(
5385
0
                props, axisList[0], axisList[1], axisList[2]));
5386
0
        }
5387
0
        throw FactoryException("invalid number of axis for SphericalCS");
5388
0
    }
5389
24
    if (csType == CS_TYPE_VERTICAL) {
5390
24
        if (axisList.size() == 1) {
5391
24
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5392
24
        }
5393
0
        throw FactoryException("invalid number of axis for VerticalCS");
5394
24
    }
5395
0
    if (csType == CS_TYPE_ORDINAL) {
5396
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5397
0
    }
5398
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5399
0
}
5400
5401
// ---------------------------------------------------------------------------
5402
5403
/** \brief Returns a crs::GeodeticCRS from the specified code.
5404
 *
5405
 * @param code Object code allocated by authority.
5406
 * @return object.
5407
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5408
 * @throw FactoryException in case of other errors.
5409
 */
5410
5411
crs::GeodeticCRSNNPtr
5412
4.56k
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5413
4.56k
    return createGeodeticCRS(code, false);
5414
4.56k
}
5415
5416
// ---------------------------------------------------------------------------
5417
5418
/** \brief Returns a crs::GeographicCRS from the specified code.
5419
 *
5420
 * @param code Object code allocated by authority.
5421
 * @return object.
5422
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5423
 * @throw FactoryException in case of other errors.
5424
 */
5425
5426
crs::GeographicCRSNNPtr
5427
142
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5428
142
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5429
142
        createGeodeticCRS(code, true)));
5430
142
    if (!crs) {
5431
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5432
0
                                           d->authority(), code);
5433
0
    }
5434
142
    return NN_NO_CHECK(crs);
5435
142
}
5436
5437
// ---------------------------------------------------------------------------
5438
5439
static crs::GeodeticCRSNNPtr
5440
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5441
0
               const util::PropertyMap &props) {
5442
0
    auto cs = geodCRS->coordinateSystem();
5443
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5444
0
    if (ellipsoidalCS) {
5445
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5446
0
                                          geodCRS->datumEnsemble(),
5447
0
                                          NN_NO_CHECK(ellipsoidalCS));
5448
0
    }
5449
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5450
0
    if (geocentricCS) {
5451
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5452
0
                                        geodCRS->datumEnsemble(),
5453
0
                                        NN_NO_CHECK(geocentricCS));
5454
0
    }
5455
0
    return geodCRS;
5456
0
}
5457
5458
// ---------------------------------------------------------------------------
5459
5460
crs::GeodeticCRSNNPtr
5461
AuthorityFactory::createGeodeticCRS(const std::string &code,
5462
4.71k
                                    bool geographicOnly) const {
5463
4.71k
    const auto cacheKey(d->authority() + code);
5464
4.71k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5465
4.71k
    if (crs) {
5466
1.85k
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5467
1.85k
        if (geogCRS) {
5468
1.85k
            return NN_NO_CHECK(geogCRS);
5469
1.85k
        }
5470
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5471
0
                                           d->authority(), code);
5472
1.85k
    }
5473
2.86k
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5474
2.86k
                    "coordinate_system_code, datum_auth_name, datum_code, "
5475
2.86k
                    "text_definition, deprecated, description FROM "
5476
2.86k
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5477
2.86k
    if (geographicOnly) {
5478
142
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5479
142
               ")";
5480
142
    }
5481
2.86k
    auto res = d->runWithCodeParam(sql, code);
5482
2.86k
    if (res.empty()) {
5483
142
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5484
142
                                           d->authority(), code);
5485
142
    }
5486
2.71k
    try {
5487
2.71k
        const auto &row = res.front();
5488
2.71k
        const auto &name = row[0];
5489
2.71k
        const auto &type = row[1];
5490
2.71k
        const auto &cs_auth_name = row[2];
5491
2.71k
        const auto &cs_code = row[3];
5492
2.71k
        const auto &datum_auth_name = row[4];
5493
2.71k
        const auto &datum_code = row[5];
5494
2.71k
        const auto &text_definition = row[6];
5495
2.71k
        const bool deprecated = row[7] == "1";
5496
2.71k
        const auto &remarks = row[8];
5497
5498
2.71k
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5499
2.71k
                                                     deprecated, remarks);
5500
5501
2.71k
        if (!text_definition.empty()) {
5502
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5503
0
            auto obj = createFromUserInput(
5504
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5505
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5506
0
            if (geodCRS) {
5507
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5508
0
                d->context()->d->cache(cacheKey, crsRet);
5509
0
                return crsRet;
5510
0
            }
5511
5512
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5513
0
            if (boundCRS) {
5514
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5515
0
                    boundCRS->baseCRS());
5516
0
                if (geodCRS) {
5517
0
                    auto newBoundCRS = crs::BoundCRS::create(
5518
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5519
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5520
0
                    return NN_NO_CHECK(
5521
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5522
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5523
0
                }
5524
0
            }
5525
5526
0
            throw FactoryException(
5527
0
                "text_definition does not define a GeodeticCRS");
5528
0
        }
5529
5530
2.71k
        auto cs =
5531
2.71k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5532
2.71k
        datum::GeodeticReferenceFramePtr datum;
5533
2.71k
        datum::DatumEnsemblePtr datumEnsemble;
5534
2.71k
        constexpr bool turnEnsembleAsDatum = false;
5535
2.71k
        d->createFactory(datum_auth_name)
5536
2.71k
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5537
2.71k
                                            turnEnsembleAsDatum);
5538
5539
2.71k
        auto ellipsoidalCS =
5540
2.71k
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5541
2.71k
        if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) {
5542
2.22k
            auto crsRet = crs::GeographicCRS::create(
5543
2.22k
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5544
2.22k
            d->context()->d->cache(cacheKey, crsRet);
5545
2.22k
            return crsRet;
5546
2.22k
        }
5547
5548
492
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5549
492
        if (type == GEOCENTRIC && geocentricCS) {
5550
484
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5551
484
                                                   NN_NO_CHECK(geocentricCS));
5552
484
            d->context()->d->cache(cacheKey, crsRet);
5553
484
            return crsRet;
5554
484
        }
5555
5556
8
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5557
8
        if (type == OTHER && sphericalCS) {
5558
8
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5559
8
                                                   NN_NO_CHECK(sphericalCS));
5560
8
            d->context()->d->cache(cacheKey, crsRet);
5561
8
            return crsRet;
5562
8
        }
5563
5564
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5565
0
                               type + ", " + cs->getWKT2Type(true));
5566
8
    } catch (const std::exception &ex) {
5567
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5568
0
    }
5569
2.71k
}
5570
5571
// ---------------------------------------------------------------------------
5572
5573
/** \brief Returns a crs::VerticalCRS from the specified code.
5574
 *
5575
 * @param code Object code allocated by authority.
5576
 * @return object.
5577
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5578
 * @throw FactoryException in case of other errors.
5579
 */
5580
5581
crs::VerticalCRSNNPtr
5582
1.05k
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5583
1.05k
    const auto cacheKey(d->authority() + code);
5584
1.05k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5585
1.05k
    if (crs) {
5586
508
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5587
508
        if (projCRS) {
5588
508
            return NN_NO_CHECK(projCRS);
5589
508
        }
5590
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5591
0
                                           d->authority(), code);
5592
508
    }
5593
550
    auto res = d->runWithCodeParam(
5594
550
        "SELECT name, coordinate_system_auth_name, "
5595
550
        "coordinate_system_code, datum_auth_name, datum_code, "
5596
550
        "deprecated FROM "
5597
550
        "vertical_crs WHERE auth_name = ? AND code = ?",
5598
550
        code);
5599
550
    if (res.empty()) {
5600
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5601
0
                                           d->authority(), code);
5602
0
    }
5603
550
    try {
5604
550
        const auto &row = res.front();
5605
550
        const auto &name = row[0];
5606
550
        const auto &cs_auth_name = row[1];
5607
550
        const auto &cs_code = row[2];
5608
550
        const auto &datum_auth_name = row[3];
5609
550
        const auto &datum_code = row[4];
5610
550
        const bool deprecated = row[5] == "1";
5611
550
        auto cs =
5612
550
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5613
550
        datum::VerticalReferenceFramePtr datum;
5614
550
        datum::DatumEnsemblePtr datumEnsemble;
5615
550
        constexpr bool turnEnsembleAsDatum = false;
5616
550
        d->createFactory(datum_auth_name)
5617
550
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5618
550
                                            turnEnsembleAsDatum);
5619
550
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5620
550
                                                     deprecated);
5621
5622
550
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5623
550
        if (verticalCS) {
5624
550
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5625
550
                                                   NN_NO_CHECK(verticalCS));
5626
550
            d->context()->d->cache(cacheKey, crsRet);
5627
550
            return crsRet;
5628
550
        }
5629
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5630
0
                               cs->getWKT2Type(true));
5631
550
    } catch (const std::exception &ex) {
5632
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5633
0
    }
5634
550
}
5635
5636
// ---------------------------------------------------------------------------
5637
5638
/** \brief Returns a crs::EngineeringCRS from the specified code.
5639
 *
5640
 * @param code Object code allocated by authority.
5641
 * @return object.
5642
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5643
 * @throw FactoryException in case of other errors.
5644
 * @since 9.6
5645
 */
5646
5647
crs::EngineeringCRSNNPtr
5648
48
AuthorityFactory::createEngineeringCRS(const std::string &code) const {
5649
48
    const auto cacheKey(d->authority() + code);
5650
48
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5651
48
    if (crs) {
5652
27
        auto engCRS = std::dynamic_pointer_cast<crs::EngineeringCRS>(crs);
5653
27
        if (engCRS) {
5654
27
            return NN_NO_CHECK(engCRS);
5655
27
        }
5656
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5657
0
                                           d->authority(), code);
5658
27
    }
5659
21
    auto res = d->runWithCodeParam(
5660
21
        "SELECT name, coordinate_system_auth_name, "
5661
21
        "coordinate_system_code, datum_auth_name, datum_code, "
5662
21
        "deprecated FROM "
5663
21
        "engineering_crs WHERE auth_name = ? AND code = ?",
5664
21
        code);
5665
21
    if (res.empty()) {
5666
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5667
0
                                           d->authority(), code);
5668
0
    }
5669
21
    try {
5670
21
        const auto &row = res.front();
5671
21
        const auto &name = row[0];
5672
21
        const auto &cs_auth_name = row[1];
5673
21
        const auto &cs_code = row[2];
5674
21
        const auto &datum_auth_name = row[3];
5675
21
        const auto &datum_code = row[4];
5676
21
        const bool deprecated = row[5] == "1";
5677
21
        auto cs =
5678
21
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5679
21
        auto datum = d->createFactory(datum_auth_name)
5680
21
                         ->createEngineeringDatum(datum_code);
5681
21
        auto props = d->createPropertiesSearchUsages("engineering_crs", code,
5682
21
                                                     name, deprecated);
5683
21
        auto crsRet = crs::EngineeringCRS::create(props, datum, cs);
5684
21
        d->context()->d->cache(cacheKey, crsRet);
5685
21
        return crsRet;
5686
21
    } catch (const std::exception &ex) {
5687
0
        throw buildFactoryException("engineeringCRS", d->authority(), code, ex);
5688
0
    }
5689
21
}
5690
5691
// ---------------------------------------------------------------------------
5692
5693
/** \brief Returns a operation::Conversion from the specified code.
5694
 *
5695
 * @param code Object code allocated by authority.
5696
 * @return object.
5697
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5698
 * @throw FactoryException in case of other errors.
5699
 */
5700
5701
operation::ConversionNNPtr
5702
1.83k
AuthorityFactory::createConversion(const std::string &code) const {
5703
5704
1.83k
    static const char *sql =
5705
1.83k
        "SELECT name, description, "
5706
1.83k
        "method_auth_name, method_code, method_name, "
5707
5708
1.83k
        "param1_auth_name, param1_code, param1_name, param1_value, "
5709
1.83k
        "param1_uom_auth_name, param1_uom_code, "
5710
5711
1.83k
        "param2_auth_name, param2_code, param2_name, param2_value, "
5712
1.83k
        "param2_uom_auth_name, param2_uom_code, "
5713
5714
1.83k
        "param3_auth_name, param3_code, param3_name, param3_value, "
5715
1.83k
        "param3_uom_auth_name, param3_uom_code, "
5716
5717
1.83k
        "param4_auth_name, param4_code, param4_name, param4_value, "
5718
1.83k
        "param4_uom_auth_name, param4_uom_code, "
5719
5720
1.83k
        "param5_auth_name, param5_code, param5_name, param5_value, "
5721
1.83k
        "param5_uom_auth_name, param5_uom_code, "
5722
5723
1.83k
        "param6_auth_name, param6_code, param6_name, param6_value, "
5724
1.83k
        "param6_uom_auth_name, param6_uom_code, "
5725
5726
1.83k
        "param7_auth_name, param7_code, param7_name, param7_value, "
5727
1.83k
        "param7_uom_auth_name, param7_uom_code, "
5728
5729
1.83k
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5730
5731
1.83k
    auto res = d->runWithCodeParam(sql, code);
5732
1.83k
    if (res.empty()) {
5733
0
        try {
5734
            // Conversions using methods Change of Vertical Unit or
5735
            // Height Depth Reversal are stored in other_transformation
5736
0
            auto op = createCoordinateOperation(
5737
0
                code, false /* allowConcatenated */,
5738
0
                false /* usePROJAlternativeGridNames */,
5739
0
                "other_transformation");
5740
0
            auto conv =
5741
0
                util::nn_dynamic_pointer_cast<operation::Conversion>(op);
5742
0
            if (conv) {
5743
0
                return NN_NO_CHECK(conv);
5744
0
            }
5745
0
        } catch (const std::exception &) {
5746
0
        }
5747
0
        throw NoSuchAuthorityCodeException("conversion not found",
5748
0
                                           d->authority(), code);
5749
0
    }
5750
1.83k
    try {
5751
1.83k
        const auto &row = res.front();
5752
1.83k
        size_t idx = 0;
5753
1.83k
        const auto &name = row[idx++];
5754
1.83k
        const auto &description = row[idx++];
5755
1.83k
        const auto &method_auth_name = row[idx++];
5756
1.83k
        const auto &method_code = row[idx++];
5757
1.83k
        const auto &method_name = row[idx++];
5758
1.83k
        const size_t base_param_idx = idx;
5759
1.83k
        std::vector<operation::OperationParameterNNPtr> parameters;
5760
1.83k
        std::vector<operation::ParameterValueNNPtr> values;
5761
11.0k
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5762
11.0k
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5763
11.0k
            if (param_auth_name.empty()) {
5764
1.79k
                break;
5765
1.79k
            }
5766
9.25k
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5767
9.25k
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5768
9.25k
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5769
9.25k
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5770
9.25k
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5771
9.25k
            parameters.emplace_back(operation::OperationParameter::create(
5772
9.25k
                util::PropertyMap()
5773
9.25k
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5774
9.25k
                    .set(metadata::Identifier::CODE_KEY, param_code)
5775
9.25k
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5776
9.25k
            std::string normalized_uom_code(param_uom_code);
5777
9.25k
            const double normalized_value = normalizeMeasure(
5778
9.25k
                param_uom_code, param_value, normalized_uom_code);
5779
9.25k
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5780
9.25k
                                              normalized_uom_code);
5781
9.25k
            values.emplace_back(operation::ParameterValue::create(
5782
9.25k
                common::Measure(normalized_value, uom)));
5783
9.25k
        }
5784
1.83k
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5785
5786
1.83k
        auto propConversion = d->createPropertiesSearchUsages(
5787
1.83k
            "conversion", code, name, deprecated);
5788
1.83k
        if (!description.empty())
5789
888
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5790
888
                               description);
5791
5792
1.83k
        auto propMethod = util::PropertyMap().set(
5793
1.83k
            common::IdentifiedObject::NAME_KEY, method_name);
5794
1.83k
        if (!method_auth_name.empty()) {
5795
1.83k
            propMethod
5796
1.83k
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5797
1.83k
                .set(metadata::Identifier::CODE_KEY, method_code);
5798
1.83k
        }
5799
5800
1.83k
        return operation::Conversion::create(propConversion, propMethod,
5801
1.83k
                                             parameters, values);
5802
1.83k
    } catch (const std::exception &ex) {
5803
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5804
0
    }
5805
1.83k
}
5806
5807
// ---------------------------------------------------------------------------
5808
5809
/** \brief Returns a crs::ProjectedCRS from the specified code.
5810
 *
5811
 * @param code Object code allocated by authority.
5812
 * @return object.
5813
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5814
 * @throw FactoryException in case of other errors.
5815
 */
5816
5817
crs::ProjectedCRSNNPtr
5818
2.47k
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5819
2.47k
    const auto cacheKey(d->authority() + code);
5820
2.47k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5821
2.47k
    if (crs) {
5822
745
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5823
745
        if (projCRS) {
5824
745
            return NN_NO_CHECK(projCRS);
5825
745
        }
5826
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5827
0
                                           d->authority(), code);
5828
745
    }
5829
1.73k
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5830
2.47k
}
5831
5832
// ---------------------------------------------------------------------------
5833
//! @cond Doxygen_Suppress
5834
5835
/** Returns the result of the SQL query needed by createProjectedCRSEnd
5836
 *
5837
 * The split in two functions is for createFromCoordinateReferenceSystemCodes()
5838
 * convenience, to avoid throwing exceptions.
5839
 */
5840
SQLResultSet
5841
1.73k
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5842
1.73k
    return runWithCodeParam(
5843
1.73k
        "SELECT name, coordinate_system_auth_name, "
5844
1.73k
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5845
1.73k
        "conversion_auth_name, conversion_code, "
5846
1.73k
        "text_definition, "
5847
1.73k
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5848
1.73k
        code);
5849
1.73k
}
5850
5851
// ---------------------------------------------------------------------------
5852
5853
/** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
5854
crs::ProjectedCRSNNPtr
5855
AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
5856
1.73k
                                                 const SQLResultSet &res) {
5857
1.73k
    const auto cacheKey(authority() + code);
5858
1.73k
    if (res.empty()) {
5859
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5860
0
                                           authority(), code);
5861
0
    }
5862
1.73k
    try {
5863
1.73k
        const auto &row = res.front();
5864
1.73k
        const auto &name = row[0];
5865
1.73k
        const auto &cs_auth_name = row[1];
5866
1.73k
        const auto &cs_code = row[2];
5867
1.73k
        const auto &geodetic_crs_auth_name = row[3];
5868
1.73k
        const auto &geodetic_crs_code = row[4];
5869
1.73k
        const auto &conversion_auth_name = row[5];
5870
1.73k
        const auto &conversion_code = row[6];
5871
1.73k
        const auto &text_definition = row[7];
5872
1.73k
        const bool deprecated = row[8] == "1";
5873
5874
1.73k
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5875
1.73k
                                                  deprecated);
5876
5877
1.73k
        if (!text_definition.empty()) {
5878
142
            DatabaseContext::Private::RecursionDetector detector(context());
5879
142
            auto obj = createFromUserInput(
5880
142
                pj_add_type_crs_if_needed(text_definition), context());
5881
142
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5882
142
            if (projCRS) {
5883
142
                auto conv = projCRS->derivingConversion();
5884
142
                auto newConv =
5885
142
                    (conv->nameStr() == "unnamed")
5886
142
                        ? operation::Conversion::create(
5887
124
                              util::PropertyMap().set(
5888
124
                                  common::IdentifiedObject::NAME_KEY, name),
5889
124
                              conv->method(), conv->parameterValues())
5890
142
                        : std::move(conv);
5891
142
                auto crsRet = crs::ProjectedCRS::create(
5892
142
                    props, projCRS->baseCRS(), newConv,
5893
142
                    projCRS->coordinateSystem());
5894
142
                context()->d->cache(cacheKey, crsRet);
5895
142
                return crsRet;
5896
142
            }
5897
5898
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5899
0
            if (boundCRS) {
5900
0
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
5901
0
                    boundCRS->baseCRS().get());
5902
0
                if (projCRS) {
5903
0
                    auto newBoundCRS = crs::BoundCRS::create(
5904
0
                        crs::ProjectedCRS::create(props, projCRS->baseCRS(),
5905
0
                                                  projCRS->derivingConversion(),
5906
0
                                                  projCRS->coordinateSystem()),
5907
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5908
0
                    return NN_NO_CHECK(
5909
0
                        util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
5910
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5911
0
                }
5912
0
            }
5913
5914
0
            throw FactoryException(
5915
0
                "text_definition does not define a ProjectedCRS");
5916
0
        }
5917
5918
1.58k
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5919
5920
1.58k
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5921
1.58k
                           ->createGeodeticCRS(geodetic_crs_code);
5922
5923
1.58k
        auto conv = createFactory(conversion_auth_name)
5924
1.58k
                        ->createConversion(conversion_code);
5925
1.58k
        if (conv->nameStr() == "unnamed") {
5926
232
            conv = conv->shallowClone();
5927
232
            conv->setProperties(util::PropertyMap().set(
5928
232
                common::IdentifiedObject::NAME_KEY, name));
5929
232
        }
5930
5931
1.58k
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5932
1.58k
        if (cartesianCS) {
5933
1.58k
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5934
1.58k
                                                    NN_NO_CHECK(cartesianCS));
5935
1.58k
            context()->d->cache(cacheKey, crsRet);
5936
1.58k
            return crsRet;
5937
1.58k
        }
5938
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5939
0
                               cs->getWKT2Type(true));
5940
1.58k
    } catch (const std::exception &ex) {
5941
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5942
0
    }
5943
1.73k
}
5944
//! @endcond
5945
5946
// ---------------------------------------------------------------------------
5947
5948
/** \brief Returns a crs::DerivedProjectedCRS from the specified code.
5949
 *
5950
 * @param code Object code allocated by authority.
5951
 * @return object.
5952
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5953
 * @throw FactoryException in case of other errors.
5954
 */
5955
crs::DerivedProjectedCRSNNPtr
5956
0
AuthorityFactory::createDerivedProjectedCRS(const std::string &code) const {
5957
0
    const auto cacheKey(d->authority() + code);
5958
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5959
0
    if (crs) {
5960
0
        auto derivedProjCRS =
5961
0
            std::dynamic_pointer_cast<crs::DerivedProjectedCRS>(crs);
5962
0
        if (derivedProjCRS) {
5963
0
            return NN_NO_CHECK(derivedProjCRS);
5964
0
        }
5965
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5966
0
                                           d->authority(), code);
5967
0
    }
5968
0
    return d->createDerivedProjectedCRSEnd(
5969
0
        code, d->createDerivedProjectedCRSBegin(code));
5970
0
}
5971
5972
// ---------------------------------------------------------------------------
5973
//! @cond Doxygen_Suppress
5974
5975
/** Returns the result of the SQL query needed by createDerivedProjectedCRSEnd
5976
 */
5977
SQLResultSet AuthorityFactory::Private::createDerivedProjectedCRSBegin(
5978
0
    const std::string &code) {
5979
0
    return runWithCodeParam(
5980
0
        "SELECT name, coordinate_system_auth_name, "
5981
0
        "coordinate_system_code, base_crs_auth_name, base_crs_code, "
5982
0
        "conversion_auth_name, conversion_code, "
5983
0
        "text_definition, "
5984
0
        "deprecated FROM derived_projected_crs WHERE auth_name = ? AND code = "
5985
0
        "?",
5986
0
        code);
5987
0
}
5988
5989
// ---------------------------------------------------------------------------
5990
5991
/** Build a DerivedProjectedCRS from the result of
5992
 * createDerivedProjectedCRSBegin() */
5993
crs::DerivedProjectedCRSNNPtr
5994
AuthorityFactory::Private::createDerivedProjectedCRSEnd(
5995
0
    const std::string &code, const SQLResultSet &res) {
5996
0
    const auto cacheKey(authority() + code);
5997
0
    if (res.empty()) {
5998
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5999
0
                                           authority(), code);
6000
0
    }
6001
0
    try {
6002
0
        const auto &row = res.front();
6003
0
        const auto &name = row[0];
6004
0
        const auto &cs_auth_name = row[1];
6005
0
        const auto &cs_code = row[2];
6006
0
        const auto &base_crs_auth_name = row[3];
6007
0
        const auto &base_crs_code = row[4];
6008
0
        const auto &conversion_auth_name = row[5];
6009
0
        const auto &conversion_code = row[6];
6010
0
        const auto &text_definition = row[7];
6011
0
        const bool deprecated = row[8] == "1";
6012
6013
0
        auto props = createPropertiesSearchUsages("derived_projected_crs", code,
6014
0
                                                  name, deprecated);
6015
6016
0
        if (!text_definition.empty()) {
6017
0
            DatabaseContext::Private::RecursionDetector detector(context());
6018
0
            auto obj = createFromUserInput(
6019
0
                pj_add_type_crs_if_needed(text_definition), context());
6020
0
            auto derivedProjCRS =
6021
0
                dynamic_cast<const crs::DerivedProjectedCRS *>(obj.get());
6022
0
            if (derivedProjCRS) {
6023
0
                auto conv = derivedProjCRS->derivingConversion();
6024
0
                auto newConv =
6025
0
                    (conv->nameStr() == "unnamed")
6026
0
                        ? operation::Conversion::create(
6027
0
                              util::PropertyMap().set(
6028
0
                                  common::IdentifiedObject::NAME_KEY, name),
6029
0
                              conv->method(), conv->parameterValues())
6030
0
                        : std::move(conv);
6031
0
                auto crsRet = crs::DerivedProjectedCRS::create(
6032
0
                    props, derivedProjCRS->baseCRS(), newConv,
6033
0
                    derivedProjCRS->coordinateSystem());
6034
0
                context()->d->cache(cacheKey, crsRet);
6035
0
                return crsRet;
6036
0
            }
6037
0
            throw FactoryException(
6038
0
                "text_definition does not define a DerivedProjectedCRS");
6039
0
        }
6040
6041
0
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
6042
6043
0
        auto baseCRS = createFactory(base_crs_auth_name)
6044
0
                           ->createProjectedCRS(base_crs_code);
6045
6046
0
        auto conv = createFactory(conversion_auth_name)
6047
0
                        ->createConversion(conversion_code);
6048
0
        if (conv->nameStr() == "unnamed") {
6049
0
            conv = conv->shallowClone();
6050
0
            conv->setProperties(util::PropertyMap().set(
6051
0
                common::IdentifiedObject::NAME_KEY, name));
6052
0
        }
6053
6054
0
        auto crsRet =
6055
0
            crs::DerivedProjectedCRS::create(props, baseCRS, conv, cs);
6056
0
        context()->d->cache(cacheKey, crsRet);
6057
0
        return crsRet;
6058
0
    } catch (const std::exception &ex) {
6059
0
        throw buildFactoryException("derivedProjectedCRS", authority(), code,
6060
0
                                    ex);
6061
0
    }
6062
0
}
6063
//! @endcond
6064
6065
// ---------------------------------------------------------------------------
6066
6067
/** \brief Returns a crs::CompoundCRS from the specified code.
6068
 *
6069
 * @param code Object code allocated by authority.
6070
 * @return object.
6071
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6072
 * @throw FactoryException in case of other errors.
6073
 */
6074
6075
crs::CompoundCRSNNPtr
6076
485
AuthorityFactory::createCompoundCRS(const std::string &code) const {
6077
485
    auto res =
6078
485
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
6079
485
                            "vertical_crs_auth_name, vertical_crs_code, "
6080
485
                            "deprecated FROM "
6081
485
                            "compound_crs WHERE auth_name = ? AND code = ?",
6082
485
                            code);
6083
485
    if (res.empty()) {
6084
0
        throw NoSuchAuthorityCodeException("compoundCRS not found",
6085
0
                                           d->authority(), code);
6086
0
    }
6087
485
    try {
6088
485
        const auto &row = res.front();
6089
485
        const auto &name = row[0];
6090
485
        const auto &horiz_crs_auth_name = row[1];
6091
485
        const auto &horiz_crs_code = row[2];
6092
485
        const auto &vertical_crs_auth_name = row[3];
6093
485
        const auto &vertical_crs_code = row[4];
6094
485
        const bool deprecated = row[5] == "1";
6095
6096
485
        auto horizCRS =
6097
485
            d->createFactory(horiz_crs_auth_name)
6098
485
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
6099
485
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
6100
485
                           ->createVerticalCRS(vertical_crs_code);
6101
6102
485
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
6103
485
                                                     deprecated);
6104
485
        return crs::CompoundCRS::create(
6105
485
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
6106
485
                                              std::move(vertCRS)});
6107
485
    } catch (const std::exception &ex) {
6108
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
6109
0
    }
6110
485
}
6111
6112
// ---------------------------------------------------------------------------
6113
6114
/** \brief Returns a crs::CRS from the specified code.
6115
 *
6116
 * @param code Object code allocated by authority.
6117
 * @return object.
6118
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6119
 * @throw FactoryException in case of other errors.
6120
 */
6121
6122
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
6123
5.50k
    const std::string &code) const {
6124
5.50k
    return createCoordinateReferenceSystem(code, true);
6125
5.50k
}
6126
6127
//! @cond Doxygen_Suppress
6128
6129
crs::CRSNNPtr
6130
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
6131
6.22k
                                                  bool allowCompound) const {
6132
6.22k
    const auto cacheKey(d->authority() + code);
6133
6.22k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
6134
6.22k
    if (crs) {
6135
3.04k
        return NN_NO_CHECK(crs);
6136
3.04k
    }
6137
6138
3.17k
    if (d->authority() == metadata::Identifier::OGC) {
6139
157
        if (code == "AnsiDate") {
6140
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
6141
18
            return crs::TemporalCRS::create(
6142
18
                util::PropertyMap()
6143
                    // above URL indicates Julian Date" as name... likely wrong
6144
18
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
6145
18
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6146
18
                    .set(metadata::Identifier::CODE_KEY, code),
6147
18
                datum::TemporalDatum::create(
6148
18
                    util::PropertyMap().set(
6149
18
                        common::IdentifiedObject::NAME_KEY,
6150
18
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
6151
18
                        "as day 1."),
6152
18
                    common::DateTime::create("1600-12-31T00:00:00Z"),
6153
18
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6154
18
                cs::TemporalCountCS::create(
6155
18
                    util::PropertyMap(),
6156
18
                    cs::CoordinateSystemAxis::create(
6157
18
                        util::PropertyMap().set(
6158
18
                            common::IdentifiedObject::NAME_KEY, "Time"),
6159
18
                        "T", cs::AxisDirection::FUTURE,
6160
18
                        common::UnitOfMeasure("day", 0,
6161
18
                                              UnitOfMeasure::Type::TIME))));
6162
18
        }
6163
139
        if (code == "JulianDate") {
6164
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
6165
0
            return crs::TemporalCRS::create(
6166
0
                util::PropertyMap()
6167
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
6168
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6169
0
                    .set(metadata::Identifier::CODE_KEY, code),
6170
0
                datum::TemporalDatum::create(
6171
0
                    util::PropertyMap().set(
6172
0
                        common::IdentifiedObject::NAME_KEY,
6173
0
                        "The beginning of the Julian period."),
6174
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
6175
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6176
0
                cs::TemporalCountCS::create(
6177
0
                    util::PropertyMap(),
6178
0
                    cs::CoordinateSystemAxis::create(
6179
0
                        util::PropertyMap().set(
6180
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6181
0
                        "T", cs::AxisDirection::FUTURE,
6182
0
                        common::UnitOfMeasure("day", 0,
6183
0
                                              UnitOfMeasure::Type::TIME))));
6184
0
        }
6185
139
        if (code == "UnixTime") {
6186
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
6187
128
            return crs::TemporalCRS::create(
6188
128
                util::PropertyMap()
6189
128
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
6190
128
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6191
128
                    .set(metadata::Identifier::CODE_KEY, code),
6192
128
                datum::TemporalDatum::create(
6193
128
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6194
128
                                            "Unix epoch"),
6195
128
                    common::DateTime::create("1970-01-01T00:00:00Z"),
6196
128
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6197
128
                cs::TemporalCountCS::create(
6198
128
                    util::PropertyMap(),
6199
128
                    cs::CoordinateSystemAxis::create(
6200
128
                        util::PropertyMap().set(
6201
128
                            common::IdentifiedObject::NAME_KEY, "Time"),
6202
128
                        "T", cs::AxisDirection::FUTURE,
6203
128
                        common::UnitOfMeasure::SECOND)));
6204
128
        }
6205
11
        if (code == "84") {
6206
1
            return createCoordinateReferenceSystem("CRS84", false);
6207
1
        }
6208
11
    }
6209
6210
3.03k
    auto res = d->runWithCodeParam(
6211
3.03k
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
6212
3.03k
    if (res.empty()) {
6213
1.50k
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
6214
1.50k
                                           code);
6215
1.50k
    }
6216
1.52k
    const auto &type = res.front()[0];
6217
1.52k
    if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC ||
6218
809
        type == OTHER) {
6219
719
        return createGeodeticCRS(code);
6220
719
    }
6221
809
    if (type == VERTICAL) {
6222
102
        return createVerticalCRS(code);
6223
102
    }
6224
707
    if (type == PROJECTED) {
6225
314
        return createProjectedCRS(code);
6226
314
    }
6227
393
    if (type == DERIVED_PROJECTED) {
6228
0
        return createDerivedProjectedCRS(code);
6229
0
    }
6230
393
    if (type == ENGINEERING) {
6231
6
        return createEngineeringCRS(code);
6232
6
    }
6233
387
    if (allowCompound && type == COMPOUND) {
6234
381
        return createCompoundCRS(code);
6235
381
    }
6236
6
    throw FactoryException("unhandled CRS type: " + type);
6237
387
}
6238
6239
//! @endcond
6240
6241
// ---------------------------------------------------------------------------
6242
6243
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
6244
 *
6245
 * @param code Object code allocated by authority.
6246
 * @return object.
6247
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6248
 * @throw FactoryException in case of other errors.
6249
 * @since 9.4
6250
 */
6251
6252
coordinates::CoordinateMetadataNNPtr
6253
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
6254
0
    auto res = d->runWithCodeParam(
6255
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
6256
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
6257
0
        code);
6258
0
    if (res.empty()) {
6259
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
6260
0
                                           d->authority(), code);
6261
0
    }
6262
0
    try {
6263
0
        const auto &row = res.front();
6264
0
        const auto &crs_auth_name = row[0];
6265
0
        const auto &crs_code = row[1];
6266
0
        const auto &crs_text_definition = row[2];
6267
0
        const auto &coordinate_epoch = row[3];
6268
6269
0
        auto l_context = d->context();
6270
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
6271
0
        auto crs =
6272
0
            !crs_auth_name.empty()
6273
0
                ? d->createFactory(crs_auth_name)
6274
0
                      ->createCoordinateReferenceSystem(crs_code)
6275
0
                      .as_nullable()
6276
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
6277
0
                      createFromUserInput(crs_text_definition, l_context));
6278
0
        if (!crs) {
6279
0
            throw FactoryException(
6280
0
                std::string("cannot build CoordinateMetadata ") +
6281
0
                d->authority() + ":" + code + ": cannot build CRS");
6282
0
        }
6283
0
        if (coordinate_epoch.empty()) {
6284
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
6285
0
        } else {
6286
0
            return coordinates::CoordinateMetadata::create(
6287
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
6288
0
                l_context.as_nullable());
6289
0
        }
6290
0
    } catch (const std::exception &ex) {
6291
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
6292
0
                                    ex);
6293
0
    }
6294
0
}
6295
6296
// ---------------------------------------------------------------------------
6297
6298
//! @cond Doxygen_Suppress
6299
6300
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
6301
4.15k
                                               int code) {
6302
4.15k
    return util::PropertyMap()
6303
4.15k
        .set(common::IdentifiedObject::NAME_KEY, name)
6304
4.15k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
6305
4.15k
        .set(metadata::Identifier::CODE_KEY, code);
6306
4.15k
}
6307
6308
// ---------------------------------------------------------------------------
6309
6310
4.15k
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
6311
4.15k
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
6312
4.15k
    assert(name);
6313
4.15k
    return operation::OperationParameter::create(
6314
4.15k
        createMapNameEPSGCode(name, code));
6315
4.15k
}
6316
6317
static operation::ParameterValueNNPtr createLength(const std::string &value,
6318
2.12k
                                                   const UnitOfMeasure &uom) {
6319
2.12k
    return operation::ParameterValue::create(
6320
2.12k
        common::Length(c_locale_stod(value), uom));
6321
2.12k
}
6322
6323
static operation::ParameterValueNNPtr createAngle(const std::string &value,
6324
1.41k
                                                  const UnitOfMeasure &uom) {
6325
1.41k
    return operation::ParameterValue::create(
6326
1.41k
        common::Angle(c_locale_stod(value), uom));
6327
1.41k
}
6328
6329
//! @endcond
6330
6331
// ---------------------------------------------------------------------------
6332
6333
/** \brief Returns a operation::CoordinateOperation from the specified code.
6334
 *
6335
 * @param code Object code allocated by authority.
6336
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6337
 * should be substituted to the official grid names.
6338
 * @return object.
6339
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6340
 * @throw FactoryException in case of other errors.
6341
 */
6342
6343
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6344
953
    const std::string &code, bool usePROJAlternativeGridNames) const {
6345
953
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
6346
953
                                     std::string());
6347
953
}
6348
6349
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6350
    const std::string &code, bool allowConcatenated,
6351
1.23k
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
6352
1.23k
    std::string type(typeIn);
6353
1.23k
    if (type.empty()) {
6354
1.23k
        auto res = d->runWithCodeParam(
6355
1.23k
            "SELECT type FROM coordinate_operation_with_conversion_view "
6356
1.23k
            "WHERE auth_name = ? AND code = ?",
6357
1.23k
            code);
6358
1.23k
        if (res.empty()) {
6359
0
            throw NoSuchAuthorityCodeException("coordinate operation not found",
6360
0
                                               d->authority(), code);
6361
0
        }
6362
1.23k
        type = res.front()[0];
6363
1.23k
    }
6364
6365
1.23k
    if (type == "conversion") {
6366
8
        return createConversion(code);
6367
8
    }
6368
6369
1.23k
    if (type == "helmert_transformation") {
6370
6371
594
        auto res = d->runWithCodeParam(
6372
594
            "SELECT name, description, "
6373
594
            "method_auth_name, method_code, method_name, "
6374
594
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6375
594
            "target_crs_code, "
6376
594
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
6377
594
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
6378
594
            "rotation_uom_code, scale_difference, "
6379
594
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
6380
594
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
6381
594
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
6382
594
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
6383
594
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
6384
594
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
6385
594
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
6386
594
            "operation_version, deprecated FROM "
6387
594
            "helmert_transformation WHERE auth_name = ? AND code = ?",
6388
594
            code);
6389
594
        if (res.empty()) {
6390
            // shouldn't happen if foreign keys are OK
6391
0
            throw NoSuchAuthorityCodeException(
6392
0
                "helmert_transformation not found", d->authority(), code);
6393
0
        }
6394
594
        try {
6395
594
            const auto &row = res.front();
6396
594
            size_t idx = 0;
6397
594
            const auto &name = row[idx++];
6398
594
            const auto &description = row[idx++];
6399
594
            const auto &method_auth_name = row[idx++];
6400
594
            const auto &method_code = row[idx++];
6401
594
            const auto &method_name = row[idx++];
6402
594
            const auto &source_crs_auth_name = row[idx++];
6403
594
            const auto &source_crs_code = row[idx++];
6404
594
            const auto &target_crs_auth_name = row[idx++];
6405
594
            const auto &target_crs_code = row[idx++];
6406
594
            const auto &accuracy = row[idx++];
6407
6408
594
            const auto &tx = row[idx++];
6409
594
            const auto &ty = row[idx++];
6410
594
            const auto &tz = row[idx++];
6411
594
            const auto &translation_uom_auth_name = row[idx++];
6412
594
            const auto &translation_uom_code = row[idx++];
6413
594
            const auto &rx = row[idx++];
6414
594
            const auto &ry = row[idx++];
6415
594
            const auto &rz = row[idx++];
6416
594
            const auto &rotation_uom_auth_name = row[idx++];
6417
594
            const auto &rotation_uom_code = row[idx++];
6418
594
            const auto &scale_difference = row[idx++];
6419
594
            const auto &scale_difference_uom_auth_name = row[idx++];
6420
594
            const auto &scale_difference_uom_code = row[idx++];
6421
6422
594
            const auto &rate_tx = row[idx++];
6423
594
            const auto &rate_ty = row[idx++];
6424
594
            const auto &rate_tz = row[idx++];
6425
594
            const auto &rate_translation_uom_auth_name = row[idx++];
6426
594
            const auto &rate_translation_uom_code = row[idx++];
6427
594
            const auto &rate_rx = row[idx++];
6428
594
            const auto &rate_ry = row[idx++];
6429
594
            const auto &rate_rz = row[idx++];
6430
594
            const auto &rate_rotation_uom_auth_name = row[idx++];
6431
594
            const auto &rate_rotation_uom_code = row[idx++];
6432
594
            const auto &rate_scale_difference = row[idx++];
6433
594
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
6434
594
            const auto &rate_scale_difference_uom_code = row[idx++];
6435
6436
594
            const auto &epoch = row[idx++];
6437
594
            const auto &epoch_uom_auth_name = row[idx++];
6438
594
            const auto &epoch_uom_code = row[idx++];
6439
6440
594
            const auto &px = row[idx++];
6441
594
            const auto &py = row[idx++];
6442
594
            const auto &pz = row[idx++];
6443
594
            const auto &pivot_uom_auth_name = row[idx++];
6444
594
            const auto &pivot_uom_code = row[idx++];
6445
6446
594
            const auto &operation_version = row[idx++];
6447
594
            const auto &deprecated_str = row[idx++];
6448
594
            const bool deprecated = deprecated_str == "1";
6449
594
            assert(idx == row.size());
6450
6451
594
            auto uom_translation = d->createUnitOfMeasure(
6452
594
                translation_uom_auth_name, translation_uom_code);
6453
6454
594
            auto uom_epoch = epoch_uom_auth_name.empty()
6455
594
                                 ? common::UnitOfMeasure::NONE
6456
594
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
6457
135
                                                          epoch_uom_code);
6458
6459
594
            auto sourceCRS =
6460
594
                d->createFactory(source_crs_auth_name)
6461
594
                    ->createCoordinateReferenceSystem(source_crs_code);
6462
594
            auto targetCRS =
6463
594
                d->createFactory(target_crs_auth_name)
6464
594
                    ->createCoordinateReferenceSystem(target_crs_code);
6465
6466
594
            std::vector<operation::OperationParameterNNPtr> parameters;
6467
594
            std::vector<operation::ParameterValueNNPtr> values;
6468
6469
594
            parameters.emplace_back(createOpParamNameEPSGCode(
6470
594
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
6471
594
            values.emplace_back(createLength(tx, uom_translation));
6472
6473
594
            parameters.emplace_back(createOpParamNameEPSGCode(
6474
594
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
6475
594
            values.emplace_back(createLength(ty, uom_translation));
6476
6477
594
            parameters.emplace_back(createOpParamNameEPSGCode(
6478
594
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
6479
594
            values.emplace_back(createLength(tz, uom_translation));
6480
6481
594
            if (!rx.empty()) {
6482
                // Helmert 7-, 8-, 10- or 15- parameter cases
6483
361
                auto uom_rotation = d->createUnitOfMeasure(
6484
361
                    rotation_uom_auth_name, rotation_uom_code);
6485
6486
361
                parameters.emplace_back(createOpParamNameEPSGCode(
6487
361
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
6488
361
                values.emplace_back(createAngle(rx, uom_rotation));
6489
6490
361
                parameters.emplace_back(createOpParamNameEPSGCode(
6491
361
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
6492
361
                values.emplace_back(createAngle(ry, uom_rotation));
6493
6494
361
                parameters.emplace_back(createOpParamNameEPSGCode(
6495
361
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
6496
361
                values.emplace_back(createAngle(rz, uom_rotation));
6497
6498
361
                auto uom_scale_difference =
6499
361
                    scale_difference_uom_auth_name.empty()
6500
361
                        ? common::UnitOfMeasure::NONE
6501
361
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
6502
361
                                                 scale_difference_uom_code);
6503
6504
361
                parameters.emplace_back(createOpParamNameEPSGCode(
6505
361
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
6506
361
                values.emplace_back(operation::ParameterValue::create(
6507
361
                    common::Scale(c_locale_stod(scale_difference),
6508
361
                                  uom_scale_difference)));
6509
361
            }
6510
6511
594
            if (!rate_tx.empty()) {
6512
                // Helmert 15-parameter
6513
6514
112
                auto uom_rate_translation = d->createUnitOfMeasure(
6515
112
                    rate_translation_uom_auth_name, rate_translation_uom_code);
6516
6517
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6518
112
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
6519
112
                values.emplace_back(
6520
112
                    createLength(rate_tx, uom_rate_translation));
6521
6522
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6523
112
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6524
112
                values.emplace_back(
6525
112
                    createLength(rate_ty, uom_rate_translation));
6526
6527
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6528
112
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6529
112
                values.emplace_back(
6530
112
                    createLength(rate_tz, uom_rate_translation));
6531
6532
112
                auto uom_rate_rotation = d->createUnitOfMeasure(
6533
112
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6534
6535
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6536
112
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6537
112
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6538
6539
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6540
112
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6541
112
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6542
6543
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6544
112
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6545
112
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6546
6547
112
                auto uom_rate_scale_difference =
6548
112
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6549
112
                                           rate_scale_difference_uom_code);
6550
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6551
112
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6552
112
                values.emplace_back(operation::ParameterValue::create(
6553
112
                    common::Scale(c_locale_stod(rate_scale_difference),
6554
112
                                  uom_rate_scale_difference)));
6555
6556
112
                parameters.emplace_back(createOpParamNameEPSGCode(
6557
112
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6558
112
                values.emplace_back(operation::ParameterValue::create(
6559
112
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6560
482
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6561
                // Helmert 8-parameter
6562
23
                parameters.emplace_back(createOpParamNameEPSGCode(
6563
23
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6564
23
                values.emplace_back(operation::ParameterValue::create(
6565
23
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6566
459
            } else if (!px.empty()) {
6567
                // Molodensky-Badekas case
6568
3
                auto uom_pivot =
6569
3
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6570
6571
3
                parameters.emplace_back(createOpParamNameEPSGCode(
6572
3
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6573
3
                values.emplace_back(createLength(px, uom_pivot));
6574
6575
3
                parameters.emplace_back(createOpParamNameEPSGCode(
6576
3
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6577
3
                values.emplace_back(createLength(py, uom_pivot));
6578
6579
3
                parameters.emplace_back(createOpParamNameEPSGCode(
6580
3
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6581
3
                values.emplace_back(createLength(pz, uom_pivot));
6582
3
            }
6583
6584
594
            auto props = d->createPropertiesSearchUsages(
6585
594
                type, code, name, deprecated, description);
6586
594
            if (!operation_version.empty()) {
6587
514
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6588
514
                          operation_version);
6589
514
            }
6590
6591
594
            auto propsMethod =
6592
594
                util::PropertyMap()
6593
594
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6594
594
                    .set(metadata::Identifier::CODE_KEY, method_code)
6595
594
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6596
6597
594
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6598
594
            if (!accuracy.empty() && accuracy != "999.0") {
6599
484
                accuracies.emplace_back(
6600
484
                    metadata::PositionalAccuracy::create(accuracy));
6601
484
            }
6602
594
            return operation::Transformation::create(
6603
594
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6604
594
                values, accuracies);
6605
6606
594
        } catch (const std::exception &ex) {
6607
0
            throw buildFactoryException("transformation", d->authority(), code,
6608
0
                                        ex);
6609
0
        }
6610
594
    }
6611
6612
636
    if (type == "grid_transformation") {
6613
369
        auto res = d->runWithCodeParam(
6614
369
            "SELECT name, description, "
6615
369
            "method_auth_name, method_code, method_name, "
6616
369
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6617
369
            "target_crs_code, "
6618
369
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6619
369
            "grid_name, "
6620
369
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6621
369
            "grid2_name, "
6622
369
            "param1_auth_name, param1_code, param1_name, param1_value, "
6623
369
            "param1_uom_auth_name, param1_uom_code, "
6624
369
            "param2_auth_name, param2_code, param2_name, param2_value, "
6625
369
            "param2_uom_auth_name, param2_uom_code, "
6626
369
            "interpolation_crs_auth_name, interpolation_crs_code, "
6627
369
            "operation_version, deprecated FROM "
6628
369
            "grid_transformation WHERE auth_name = ? AND code = ?",
6629
369
            code);
6630
369
        if (res.empty()) {
6631
            // shouldn't happen if foreign keys are OK
6632
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6633
0
                                               d->authority(), code);
6634
0
        }
6635
369
        try {
6636
369
            const auto &row = res.front();
6637
369
            size_t idx = 0;
6638
369
            const auto &name = row[idx++];
6639
369
            const auto &description = row[idx++];
6640
369
            const auto &method_auth_name = row[idx++];
6641
369
            const auto &method_code = row[idx++];
6642
369
            const auto &method_name = row[idx++];
6643
369
            const auto &source_crs_auth_name = row[idx++];
6644
369
            const auto &source_crs_code = row[idx++];
6645
369
            const auto &target_crs_auth_name = row[idx++];
6646
369
            const auto &target_crs_code = row[idx++];
6647
369
            const auto &accuracy = row[idx++];
6648
369
            const auto &grid_param_auth_name = row[idx++];
6649
369
            const auto &grid_param_code = row[idx++];
6650
369
            const auto &grid_param_name = row[idx++];
6651
369
            const auto &grid_name = row[idx++];
6652
369
            const auto &grid2_param_auth_name = row[idx++];
6653
369
            const auto &grid2_param_code = row[idx++];
6654
369
            const auto &grid2_param_name = row[idx++];
6655
369
            const auto &grid2_name = row[idx++];
6656
369
            std::vector<operation::OperationParameterNNPtr> parameters;
6657
369
            std::vector<operation::ParameterValueNNPtr> values;
6658
6659
369
            parameters.emplace_back(operation::OperationParameter::create(
6660
369
                util::PropertyMap()
6661
369
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6662
369
                    .set(metadata::Identifier::CODESPACE_KEY,
6663
369
                         grid_param_auth_name)
6664
369
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6665
369
            values.emplace_back(
6666
369
                operation::ParameterValue::createFilename(grid_name));
6667
369
            if (!grid2_name.empty()) {
6668
53
                parameters.emplace_back(operation::OperationParameter::create(
6669
53
                    util::PropertyMap()
6670
53
                        .set(common::IdentifiedObject::NAME_KEY,
6671
53
                             grid2_param_name)
6672
53
                        .set(metadata::Identifier::CODESPACE_KEY,
6673
53
                             grid2_param_auth_name)
6674
53
                        .set(metadata::Identifier::CODE_KEY,
6675
53
                             grid2_param_code)));
6676
53
                values.emplace_back(
6677
53
                    operation::ParameterValue::createFilename(grid2_name));
6678
53
            }
6679
6680
369
            const size_t base_param_idx = idx;
6681
369
            constexpr size_t N_MAX_PARAMS_GRID_TRANSFORMATION = 2;
6682
372
            for (size_t i = 0; i < N_MAX_PARAMS_GRID_TRANSFORMATION; ++i) {
6683
372
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6684
372
                if (param_auth_name.empty()) {
6685
369
                    break;
6686
369
                }
6687
3
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6688
3
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6689
3
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6690
3
                const auto &param_uom_auth_name =
6691
3
                    row[base_param_idx + i * 6 + 4];
6692
3
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6693
3
                parameters.emplace_back(operation::OperationParameter::create(
6694
3
                    util::PropertyMap()
6695
3
                        .set(metadata::Identifier::CODESPACE_KEY,
6696
3
                             param_auth_name)
6697
3
                        .set(metadata::Identifier::CODE_KEY, param_code)
6698
3
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6699
3
                std::string normalized_uom_code(param_uom_code);
6700
3
                const double normalized_value = normalizeMeasure(
6701
3
                    param_uom_code, param_value, normalized_uom_code);
6702
3
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6703
3
                                                  normalized_uom_code);
6704
3
                values.emplace_back(operation::ParameterValue::create(
6705
3
                    common::Measure(normalized_value, uom)));
6706
3
            }
6707
369
            idx = base_param_idx + 6 * N_MAX_PARAMS_GRID_TRANSFORMATION;
6708
6709
369
            const auto &interpolation_crs_auth_name = row[idx++];
6710
369
            const auto &interpolation_crs_code = row[idx++];
6711
369
            const auto &operation_version = row[idx++];
6712
369
            const auto &deprecated_str = row[idx++];
6713
369
            const bool deprecated = deprecated_str == "1";
6714
369
            assert(idx == row.size());
6715
6716
369
            auto sourceCRS =
6717
369
                d->createFactory(source_crs_auth_name)
6718
369
                    ->createCoordinateReferenceSystem(source_crs_code);
6719
369
            auto targetCRS =
6720
369
                d->createFactory(target_crs_auth_name)
6721
369
                    ->createCoordinateReferenceSystem(target_crs_code);
6722
369
            auto interpolationCRS =
6723
369
                interpolation_crs_auth_name.empty()
6724
369
                    ? nullptr
6725
369
                    : d->createFactory(interpolation_crs_auth_name)
6726
98
                          ->createCoordinateReferenceSystem(
6727
98
                              interpolation_crs_code)
6728
98
                          .as_nullable();
6729
6730
369
            auto props = d->createPropertiesSearchUsages(
6731
369
                type, code, name, deprecated, description);
6732
369
            if (!operation_version.empty()) {
6733
345
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6734
345
                          operation_version);
6735
345
            }
6736
369
            auto propsMethod =
6737
369
                util::PropertyMap()
6738
369
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6739
369
                    .set(metadata::Identifier::CODE_KEY, method_code)
6740
369
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6741
6742
369
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6743
369
            if (!accuracy.empty() && accuracy != "999.0") {
6744
363
                accuracies.emplace_back(
6745
363
                    metadata::PositionalAccuracy::create(accuracy));
6746
363
            }
6747
6748
            // A bit fragile to detect the operation type with the method name,
6749
            // but not worth changing the database model
6750
369
            if (starts_with(method_name, "Point motion")) {
6751
8
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6752
0
                    throw operation::InvalidOperation(
6753
0
                        "source_crs and target_crs should be the same for a "
6754
0
                        "PointMotionOperation");
6755
0
                }
6756
6757
8
                auto pmo = operation::PointMotionOperation::create(
6758
8
                    props, sourceCRS, propsMethod, parameters, values,
6759
8
                    accuracies);
6760
8
                if (usePROJAlternativeGridNames) {
6761
8
                    return pmo->substitutePROJAlternativeGridNames(
6762
8
                        d->context());
6763
8
                }
6764
0
                return pmo;
6765
8
            }
6766
6767
361
            auto transf = operation::Transformation::create(
6768
361
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6769
361
                parameters, values, accuracies);
6770
361
            if (usePROJAlternativeGridNames) {
6771
361
                return transf->substitutePROJAlternativeGridNames(d->context());
6772
361
            }
6773
0
            return transf;
6774
6775
361
        } catch (const std::exception &ex) {
6776
0
            throw buildFactoryException("transformation", d->authority(), code,
6777
0
                                        ex);
6778
0
        }
6779
369
    }
6780
6781
267
    if (type == "other_transformation") {
6782
156
        std::ostringstream buffer;
6783
156
        buffer.imbue(std::locale::classic());
6784
156
        buffer
6785
156
            << "SELECT name, description, "
6786
156
               "method_auth_name, method_code, method_name, "
6787
156
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6788
156
               "target_crs_code, "
6789
156
               "grid_param_auth_name, grid_param_code, grid_param_name, "
6790
156
               "grid_name, "
6791
156
               "interpolation_crs_auth_name, interpolation_crs_code, "
6792
156
               "operation_version, accuracy, deprecated";
6793
156
        constexpr int N_MAX_PARAMS_OTHER_TRANSFORMATION = 9;
6794
1.56k
        for (size_t i = 1; i <= N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6795
1.40k
            buffer << ", param" << i << "_auth_name";
6796
1.40k
            buffer << ", param" << i << "_code";
6797
1.40k
            buffer << ", param" << i << "_name";
6798
1.40k
            buffer << ", param" << i << "_value";
6799
1.40k
            buffer << ", param" << i << "_uom_auth_name";
6800
1.40k
            buffer << ", param" << i << "_uom_code";
6801
1.40k
        }
6802
156
        buffer << " FROM other_transformation "
6803
156
                  "WHERE auth_name = ? AND code = ?";
6804
6805
156
        auto res = d->runWithCodeParam(buffer.str(), code);
6806
156
        if (res.empty()) {
6807
            // shouldn't happen if foreign keys are OK
6808
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6809
0
                                               d->authority(), code);
6810
0
        }
6811
156
        try {
6812
156
            const auto &row = res.front();
6813
156
            size_t idx = 0;
6814
156
            const auto &name = row[idx++];
6815
156
            const auto &description = row[idx++];
6816
156
            const auto &method_auth_name = row[idx++];
6817
156
            const auto &method_code = row[idx++];
6818
156
            const auto &method_name = row[idx++];
6819
156
            const auto &source_crs_auth_name = row[idx++];
6820
156
            const auto &source_crs_code = row[idx++];
6821
156
            const auto &target_crs_auth_name = row[idx++];
6822
156
            const auto &target_crs_code = row[idx++];
6823
156
            const auto &grid_param_auth_name = row[idx++];
6824
156
            const auto &grid_param_code = row[idx++];
6825
156
            const auto &grid_param_name = row[idx++];
6826
156
            const auto &grid_name = row[idx++];
6827
156
            const auto &interpolation_crs_auth_name = row[idx++];
6828
156
            const auto &interpolation_crs_code = row[idx++];
6829
156
            const auto &operation_version = row[idx++];
6830
156
            const auto &accuracy = row[idx++];
6831
156
            const auto &deprecated_str = row[idx++];
6832
156
            const bool deprecated = deprecated_str == "1";
6833
6834
156
            const size_t base_param_idx = idx;
6835
156
            std::vector<operation::OperationParameterNNPtr> parameters;
6836
156
            std::vector<operation::ParameterValueNNPtr> values;
6837
385
            for (size_t i = 0; i < N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6838
385
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6839
385
                if (param_auth_name.empty()) {
6840
156
                    break;
6841
156
                }
6842
229
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6843
229
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6844
229
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6845
229
                const auto &param_uom_auth_name =
6846
229
                    row[base_param_idx + i * 6 + 4];
6847
229
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6848
6849
229
                parameters.emplace_back(operation::OperationParameter::create(
6850
229
                    util::PropertyMap()
6851
229
                        .set(metadata::Identifier::CODESPACE_KEY,
6852
229
                             param_auth_name)
6853
229
                        .set(metadata::Identifier::CODE_KEY, param_code)
6854
229
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6855
229
                std::string normalized_uom_code(param_uom_code);
6856
229
                const double normalized_value = normalizeMeasure(
6857
229
                    param_uom_code, param_value, normalized_uom_code);
6858
229
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6859
229
                                                  normalized_uom_code);
6860
229
                values.emplace_back(operation::ParameterValue::create(
6861
229
                    common::Measure(normalized_value, uom)));
6862
229
            }
6863
156
            idx = base_param_idx + 6 * N_MAX_PARAMS_OTHER_TRANSFORMATION;
6864
156
            (void)idx;
6865
156
            assert(idx == row.size());
6866
6867
156
            if (!grid_name.empty()) {
6868
0
                parameters.emplace_back(operation::OperationParameter::create(
6869
0
                    util::PropertyMap()
6870
0
                        .set(common::IdentifiedObject::NAME_KEY,
6871
0
                             grid_param_name)
6872
0
                        .set(metadata::Identifier::CODESPACE_KEY,
6873
0
                             grid_param_auth_name)
6874
0
                        .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6875
0
                values.emplace_back(
6876
0
                    operation::ParameterValue::createFilename(grid_name));
6877
0
            }
6878
6879
156
            auto sourceCRS =
6880
156
                d->createFactory(source_crs_auth_name)
6881
156
                    ->createCoordinateReferenceSystem(source_crs_code);
6882
156
            auto targetCRS =
6883
156
                d->createFactory(target_crs_auth_name)
6884
156
                    ->createCoordinateReferenceSystem(target_crs_code);
6885
156
            auto interpolationCRS =
6886
156
                interpolation_crs_auth_name.empty()
6887
156
                    ? nullptr
6888
156
                    : d->createFactory(interpolation_crs_auth_name)
6889
4
                          ->createCoordinateReferenceSystem(
6890
4
                              interpolation_crs_code)
6891
4
                          .as_nullable();
6892
6893
156
            auto props = d->createPropertiesSearchUsages(
6894
156
                type, code, name, deprecated, description);
6895
156
            if (!operation_version.empty()) {
6896
133
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6897
133
                          operation_version);
6898
133
            }
6899
6900
156
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6901
156
            if (!accuracy.empty() && accuracy != "999.0") {
6902
148
                accuracies.emplace_back(
6903
148
                    metadata::PositionalAccuracy::create(accuracy));
6904
148
            }
6905
6906
156
            if (method_auth_name == "PROJ") {
6907
43
                if (method_code == "PROJString") {
6908
43
                    auto op = operation::SingleOperation::createPROJBased(
6909
43
                        props, method_name, sourceCRS, targetCRS, accuracies);
6910
43
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6911
43
                    return op;
6912
43
                } else if (method_code == "WKT") {
6913
0
                    auto op = util::nn_dynamic_pointer_cast<
6914
0
                        operation::CoordinateOperation>(
6915
0
                        WKTParser().createFromWKT(method_name));
6916
0
                    if (!op) {
6917
0
                        throw FactoryException("WKT string does not express a "
6918
0
                                               "coordinate operation");
6919
0
                    }
6920
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6921
0
                    return NN_NO_CHECK(op);
6922
0
                }
6923
43
            }
6924
6925
113
            auto propsMethod =
6926
113
                util::PropertyMap()
6927
113
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6928
113
                    .set(metadata::Identifier::CODE_KEY, method_code)
6929
113
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6930
6931
113
            if (method_auth_name == metadata::Identifier::EPSG) {
6932
113
                int method_code_int = std::atoi(method_code.c_str());
6933
113
                if (operation::isAxisOrderReversal(method_code_int) ||
6934
112
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6935
112
                    method_code_int ==
6936
112
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6937
112
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6938
8
                    auto op = operation::Conversion::create(props, propsMethod,
6939
8
                                                            parameters, values);
6940
8
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6941
8
                    return op;
6942
8
                }
6943
113
            }
6944
105
            auto transf = operation::Transformation::create(
6945
105
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6946
105
                parameters, values, accuracies);
6947
105
            if (usePROJAlternativeGridNames) {
6948
105
                return transf->substitutePROJAlternativeGridNames(d->context());
6949
105
            }
6950
0
            return transf;
6951
6952
105
        } catch (const std::exception &ex) {
6953
0
            throw buildFactoryException("transformation", d->authority(), code,
6954
0
                                        ex);
6955
0
        }
6956
156
    }
6957
6958
111
    if (allowConcatenated && type == "concatenated_operation") {
6959
111
        auto res = d->runWithCodeParam(
6960
111
            "SELECT name, description, "
6961
111
            "source_crs_auth_name, source_crs_code, "
6962
111
            "target_crs_auth_name, target_crs_code, "
6963
111
            "accuracy, "
6964
111
            "operation_version, deprecated FROM "
6965
111
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6966
111
            code);
6967
111
        if (res.empty()) {
6968
            // shouldn't happen if foreign keys are OK
6969
0
            throw NoSuchAuthorityCodeException(
6970
0
                "concatenated_operation not found", d->authority(), code);
6971
0
        }
6972
6973
111
        auto resSteps = d->runWithCodeParam(
6974
111
            "SELECT step_auth_name, step_code, step_direction FROM "
6975
111
            "concatenated_operation_step WHERE operation_auth_name = ? "
6976
111
            "AND operation_code = ? ORDER BY step_number",
6977
111
            code);
6978
6979
111
        try {
6980
111
            const auto &row = res.front();
6981
111
            size_t idx = 0;
6982
111
            const auto &name = row[idx++];
6983
111
            const auto &description = row[idx++];
6984
111
            const auto &source_crs_auth_name = row[idx++];
6985
111
            const auto &source_crs_code = row[idx++];
6986
111
            const auto &target_crs_auth_name = row[idx++];
6987
111
            const auto &target_crs_code = row[idx++];
6988
111
            const auto &accuracy = row[idx++];
6989
111
            const auto &operation_version = row[idx++];
6990
111
            const auto &deprecated_str = row[idx++];
6991
111
            const bool deprecated = deprecated_str == "1";
6992
6993
111
            std::vector<operation::CoordinateOperationNNPtr> operations;
6994
111
            size_t countExplicitDirection = 0;
6995
285
            for (const auto &rowStep : resSteps) {
6996
285
                const auto &step_auth_name = rowStep[0];
6997
285
                const auto &step_code = rowStep[1];
6998
285
                const auto &step_direction = rowStep[2];
6999
285
                auto stepOp =
7000
285
                    d->createFactory(step_auth_name)
7001
285
                        ->createCoordinateOperation(step_code, false,
7002
285
                                                    usePROJAlternativeGridNames,
7003
285
                                                    std::string());
7004
285
                if (step_direction == "forward") {
7005
183
                    ++countExplicitDirection;
7006
183
                    operations.push_back(std::move(stepOp));
7007
183
                } else if (step_direction == "reverse") {
7008
6
                    ++countExplicitDirection;
7009
6
                    operations.push_back(stepOp->inverse());
7010
96
                } else {
7011
96
                    operations.push_back(std::move(stepOp));
7012
96
                }
7013
285
            }
7014
7015
111
            if (countExplicitDirection > 0 &&
7016
64
                countExplicitDirection != resSteps.size()) {
7017
0
                throw FactoryException("not all steps have a defined direction "
7018
0
                                       "for concatenated operation " +
7019
0
                                       code);
7020
0
            }
7021
7022
111
            const bool fixDirectionAllowed = (countExplicitDirection == 0);
7023
111
            operation::ConcatenatedOperation::fixSteps(
7024
111
                d->createFactory(source_crs_auth_name)
7025
111
                    ->createCoordinateReferenceSystem(source_crs_code),
7026
111
                d->createFactory(target_crs_auth_name)
7027
111
                    ->createCoordinateReferenceSystem(target_crs_code),
7028
111
                operations, d->context(), fixDirectionAllowed);
7029
7030
111
            auto props = d->createPropertiesSearchUsages(
7031
111
                type, code, name, deprecated, description);
7032
111
            if (!operation_version.empty()) {
7033
59
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
7034
59
                          operation_version);
7035
59
            }
7036
7037
111
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
7038
111
            if (!accuracy.empty()) {
7039
108
                if (accuracy != "999.0") {
7040
102
                    accuracies.emplace_back(
7041
102
                        metadata::PositionalAccuracy::create(accuracy));
7042
102
                }
7043
108
            } else {
7044
                // Try to compute a reasonable accuracy from the members
7045
3
                double totalAcc = -1;
7046
3
                try {
7047
8
                    for (const auto &op : operations) {
7048
8
                        auto accs = op->coordinateOperationAccuracies();
7049
8
                        if (accs.size() == 1) {
7050
6
                            double acc = c_locale_stod(accs[0]->value());
7051
6
                            if (totalAcc < 0) {
7052
3
                                totalAcc = acc;
7053
3
                            } else {
7054
3
                                totalAcc += acc;
7055
3
                            }
7056
6
                        } else if (dynamic_cast<const operation::Conversion *>(
7057
2
                                       op.get())) {
7058
                            // A conversion is perfectly accurate.
7059
2
                            if (totalAcc < 0) {
7060
0
                                totalAcc = 0;
7061
0
                            }
7062
2
                        } else {
7063
0
                            totalAcc = -1;
7064
0
                            break;
7065
0
                        }
7066
8
                    }
7067
3
                    if (totalAcc >= 0) {
7068
3
                        accuracies.emplace_back(
7069
3
                            metadata::PositionalAccuracy::create(
7070
3
                                toString(totalAcc)));
7071
3
                    }
7072
3
                } catch (const std::exception &) {
7073
0
                }
7074
3
            }
7075
111
            return operation::ConcatenatedOperation::create(props, operations,
7076
111
                                                            accuracies);
7077
7078
111
        } catch (const std::exception &ex) {
7079
0
            throw buildFactoryException("transformation", d->authority(), code,
7080
0
                                        ex);
7081
0
        }
7082
111
    }
7083
7084
0
    throw FactoryException("unhandled coordinate operation type: " + type);
7085
111
}
7086
7087
// ---------------------------------------------------------------------------
7088
7089
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7090
 *
7091
 * The list is ordered with preferred operations first. No attempt is made
7092
 * at inferring operations that are not explicitly in the database.
7093
 *
7094
 * Deprecated operations are rejected.
7095
 *
7096
 * @param sourceCRSCode Source CRS code allocated by authority.
7097
 * @param targetCRSCode Source CRS code allocated by authority.
7098
 * @return list of coordinate operations
7099
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7100
 * @throw FactoryException in case of other errors.
7101
 */
7102
7103
std::vector<operation::CoordinateOperationNNPtr>
7104
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7105
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
7106
0
    return createFromCoordinateReferenceSystemCodes(
7107
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
7108
0
        false, false, false);
7109
0
}
7110
7111
// ---------------------------------------------------------------------------
7112
7113
/** \brief Returns a list of geoid models available for that crs
7114
 *
7115
 * The list includes the geoid models connected directly with the crs,
7116
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
7117
 *
7118
 * @param code crs code allocated by authority.
7119
 * @return list of geoid model names
7120
 * @throw FactoryException in case of error.
7121
 */
7122
7123
std::list<std::string>
7124
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
7125
7126
0
    ListOfParams params;
7127
0
    std::string sql;
7128
0
    sql += "SELECT DISTINCT GM0.name "
7129
0
           "  FROM geoid_model GM0 "
7130
0
           "INNER JOIN grid_transformation GT0 "
7131
0
           "  ON  GT0.code = GM0.operation_code "
7132
0
           "  AND GT0.auth_name = GM0.operation_auth_name "
7133
0
           "  AND GT0.deprecated = 0 "
7134
0
           "INNER JOIN vertical_crs VC0 "
7135
0
           "  ON VC0.code = GT0.target_crs_code "
7136
0
           "  AND VC0.auth_name = GT0.target_crs_auth_name "
7137
0
           "INNER JOIN vertical_crs VC1 "
7138
0
           "  ON VC1.datum_code = VC0.datum_code "
7139
0
           "  AND VC1.datum_auth_name = VC0.datum_auth_name "
7140
0
           "  AND VC1.code = ? ";
7141
0
    params.emplace_back(code);
7142
0
    if (d->hasAuthorityRestriction()) {
7143
0
        sql += " AND GT0.target_crs_auth_name = ? ";
7144
0
        params.emplace_back(d->authority());
7145
0
    }
7146
0
    sql += " ORDER BY 1 ";
7147
7148
0
    auto sqlRes = d->run(sql, params);
7149
0
    std::list<std::string> res;
7150
0
    for (const auto &row : sqlRes) {
7151
0
        res.push_back(row[0]);
7152
0
    }
7153
0
    return res;
7154
0
}
7155
7156
// ---------------------------------------------------------------------------
7157
7158
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7159
 *
7160
 * The list is ordered with preferred operations first. No attempt is made
7161
 * at inferring operations that are not explicitly in the database (see
7162
 * createFromCRSCodesWithIntermediates() for that), and only
7163
 * source -> target operations are searched (i.e. if target -> source is
7164
 * present, you need to call this method with the arguments reversed, and apply
7165
 * the reverse transformations).
7166
 *
7167
 * Deprecated operations are rejected.
7168
 *
7169
 * If getAuthority() returns empty, then coordinate operations from all
7170
 * authorities are considered.
7171
 *
7172
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7173
 * @param sourceCRSCode Source CRS code allocated by authority
7174
 * sourceCRSAuthName.
7175
 * @param targetCRSAuthName Authority name of targetCRSCode
7176
 * @param targetCRSCode Source CRS code allocated by authority
7177
 * targetCRSAuthName.
7178
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7179
 * should be substituted to the official grid names.
7180
 * @param discardIfMissingGrid Whether coordinate operations that reference
7181
 * missing grids should be removed from the result set.
7182
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7183
 * as available (typically when network is enabled).
7184
 * @param discardSuperseded Whether coordinate operations that are superseded
7185
 * (but not deprecated) should be removed from the result set.
7186
 * @param tryReverseOrder whether to search in the reverse order too (and thus
7187
 * inverse results found that way)
7188
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
7189
 * intersectingExtent2 should be honored in a strict way.
7190
 * @param intersectingExtent1 Optional extent that the resulting operations
7191
 * must intersect.
7192
 * @param intersectingExtent2 Optional extent that the resulting operations
7193
 * must intersect.
7194
 * @return list of coordinate operations
7195
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7196
 * @throw FactoryException in case of other errors.
7197
 */
7198
7199
std::vector<operation::CoordinateOperationNNPtr>
7200
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7201
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7202
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7203
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7204
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7205
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
7206
    const metadata::ExtentPtr &intersectingExtent1,
7207
0
    const metadata::ExtentPtr &intersectingExtent2) const {
7208
7209
0
    auto cacheKey(d->authority());
7210
0
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
7211
0
    cacheKey += sourceCRSCode;
7212
0
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
7213
0
    cacheKey += targetCRSCode;
7214
0
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
7215
0
    cacheKey += (discardIfMissingGrid ? '1' : '0');
7216
0
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
7217
0
    cacheKey += (discardSuperseded ? '1' : '0');
7218
0
    cacheKey += (tryReverseOrder ? '1' : '0');
7219
0
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
7220
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7221
0
        if (extent) {
7222
0
            const auto &geogExtent = extent->geographicElements();
7223
0
            if (geogExtent.size() == 1) {
7224
0
                auto bbox =
7225
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7226
0
                        geogExtent[0].get());
7227
0
                if (bbox) {
7228
0
                    cacheKey += toString(bbox->southBoundLatitude());
7229
0
                    cacheKey += toString(bbox->westBoundLongitude());
7230
0
                    cacheKey += toString(bbox->northBoundLatitude());
7231
0
                    cacheKey += toString(bbox->eastBoundLongitude());
7232
0
                }
7233
0
            }
7234
0
        }
7235
0
    }
7236
7237
0
    std::vector<operation::CoordinateOperationNNPtr> list;
7238
7239
0
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
7240
0
        return list;
7241
0
    }
7242
7243
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
7244
    // In which case use the conversion of the ProjectedCRS
7245
0
    if (!targetCRSAuthName.empty()) {
7246
0
        auto targetFactory = d->createFactory(targetCRSAuthName);
7247
0
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
7248
0
                                        targetCRSCode);
7249
0
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
7250
0
            cacheKeyProjectedCRS);
7251
0
        crs::ProjectedCRSPtr targetProjCRS;
7252
0
        if (crs) {
7253
0
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
7254
0
        } else {
7255
0
            const auto sqlRes =
7256
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
7257
0
            if (!sqlRes.empty()) {
7258
0
                try {
7259
0
                    targetProjCRS =
7260
0
                        targetFactory->d
7261
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
7262
0
                            .as_nullable();
7263
0
                } catch (const std::exception &) {
7264
0
                }
7265
0
            }
7266
0
        }
7267
0
        if (targetProjCRS) {
7268
0
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
7269
0
            if (sourceCRSAuthName.empty() ||
7270
0
                (!baseIds.empty() &&
7271
0
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
7272
0
                 baseIds.front()->code() == sourceCRSCode)) {
7273
0
                bool ok = true;
7274
0
                auto conv = targetProjCRS->derivingConversion();
7275
0
                if (d->hasAuthorityRestriction()) {
7276
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
7277
0
                         d->authority();
7278
0
                }
7279
0
                if (ok) {
7280
0
                    list.emplace_back(conv);
7281
0
                    d->context()->d->cache(cacheKey, list);
7282
0
                    return list;
7283
0
                }
7284
0
            }
7285
0
        }
7286
0
    }
7287
7288
0
    std::string sql;
7289
0
    if (discardSuperseded) {
7290
0
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
7291
0
              "cov.target_crs_auth_name, cov.target_crs_code, "
7292
0
              "cov.auth_name, cov.code, cov.table_name, "
7293
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7294
0
              "extent.east_lon, "
7295
0
              "ss.replacement_auth_name, ss.replacement_code, "
7296
0
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
7297
0
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
7298
0
              "FROM "
7299
0
              "coordinate_operation_view cov "
7300
0
              "JOIN usage ON "
7301
0
              "usage.object_table_name = cov.table_name AND "
7302
0
              "usage.object_auth_name = cov.auth_name AND "
7303
0
              "usage.object_code = cov.code "
7304
0
              "JOIN extent "
7305
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7306
0
              "extent.code = usage.extent_code "
7307
0
              "LEFT JOIN supersession ss ON "
7308
0
              "ss.superseded_table_name = cov.table_name AND "
7309
0
              "ss.superseded_auth_name = cov.auth_name AND "
7310
0
              "ss.superseded_code = cov.code AND "
7311
0
              "ss.superseded_table_name = ss.replacement_table_name AND "
7312
0
              "ss.same_source_target_crs = 1 "
7313
0
              "LEFT JOIN grid_transformation gt ON "
7314
0
              "gt.auth_name = ss.replacement_auth_name AND "
7315
0
              "gt.code = ss.replacement_code "
7316
0
              "LEFT JOIN grid_alternatives ga ON "
7317
0
              "ga.original_grid_name = gt.grid_name "
7318
0
              "WHERE ";
7319
0
    } else {
7320
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
7321
0
              "target_crs_auth_name, target_crs_code, "
7322
0
              "cov.auth_name, cov.code, cov.table_name, "
7323
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7324
0
              "extent.east_lon "
7325
0
              "FROM "
7326
0
              "coordinate_operation_view cov "
7327
0
              "JOIN usage ON "
7328
0
              "usage.object_table_name = cov.table_name AND "
7329
0
              "usage.object_auth_name = cov.auth_name AND "
7330
0
              "usage.object_code = cov.code "
7331
0
              "JOIN extent "
7332
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7333
0
              "extent.code = usage.extent_code "
7334
0
              "WHERE ";
7335
0
    }
7336
0
    ListOfParams params;
7337
0
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
7338
0
        if (tryReverseOrder) {
7339
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7340
0
                   "AND "
7341
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
7342
0
                   "OR "
7343
0
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7344
0
                   "AND "
7345
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
7346
0
                   "AND ";
7347
0
            params.emplace_back(sourceCRSAuthName);
7348
0
            params.emplace_back(sourceCRSCode);
7349
0
            params.emplace_back(targetCRSAuthName);
7350
0
            params.emplace_back(targetCRSCode);
7351
0
            params.emplace_back(targetCRSAuthName);
7352
0
            params.emplace_back(targetCRSCode);
7353
0
            params.emplace_back(sourceCRSAuthName);
7354
0
            params.emplace_back(sourceCRSCode);
7355
0
        } else {
7356
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7357
0
                   "AND "
7358
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7359
0
                   "AND ";
7360
0
            params.emplace_back(sourceCRSAuthName);
7361
0
            params.emplace_back(sourceCRSCode);
7362
0
            params.emplace_back(targetCRSAuthName);
7363
0
            params.emplace_back(targetCRSCode);
7364
0
        }
7365
0
    } else if (!sourceCRSAuthName.empty()) {
7366
0
        if (tryReverseOrder) {
7367
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7368
0
                   ")OR "
7369
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7370
0
                   " AND ";
7371
0
            params.emplace_back(sourceCRSAuthName);
7372
0
            params.emplace_back(sourceCRSCode);
7373
0
            params.emplace_back(sourceCRSAuthName);
7374
0
            params.emplace_back(sourceCRSCode);
7375
0
        } else {
7376
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7377
0
                   "AND ";
7378
0
            params.emplace_back(sourceCRSAuthName);
7379
0
            params.emplace_back(sourceCRSCode);
7380
0
        }
7381
0
    } else if (!targetCRSAuthName.empty()) {
7382
0
        if (tryReverseOrder) {
7383
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
7384
0
                   " OR "
7385
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7386
0
                   " AND ";
7387
0
            params.emplace_back(targetCRSAuthName);
7388
0
            params.emplace_back(targetCRSCode);
7389
0
            params.emplace_back(targetCRSAuthName);
7390
0
            params.emplace_back(targetCRSCode);
7391
0
        } else {
7392
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7393
0
                   "AND ";
7394
0
            params.emplace_back(targetCRSAuthName);
7395
0
            params.emplace_back(targetCRSCode);
7396
0
        }
7397
0
    }
7398
0
    sql += "cov.deprecated = 0";
7399
0
    if (d->hasAuthorityRestriction()) {
7400
0
        sql += " AND cov.auth_name = ?";
7401
0
        params.emplace_back(d->authority());
7402
0
    }
7403
0
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
7404
0
           "east_lon) DESC, "
7405
0
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
7406
0
    auto res = d->run(sql, params);
7407
0
    std::set<std::pair<std::string, std::string>> setTransf;
7408
0
    if (discardSuperseded) {
7409
0
        for (const auto &row : res) {
7410
0
            const auto &auth_name = row[4];
7411
0
            const auto &code = row[5];
7412
0
            setTransf.insert(
7413
0
                std::pair<std::string, std::string>(auth_name, code));
7414
0
        }
7415
0
    }
7416
7417
    // Do a pass to determine if there are transformations that intersect
7418
    // intersectingExtent1 & intersectingExtent2
7419
0
    std::vector<bool> intersectingTransformations;
7420
0
    intersectingTransformations.resize(res.size());
7421
0
    bool hasIntersectingTransformations = false;
7422
0
    size_t i = 0;
7423
0
    for (const auto &row : res) {
7424
0
        size_t thisI = i;
7425
0
        ++i;
7426
0
        if (discardSuperseded) {
7427
0
            const auto &replacement_auth_name = row[11];
7428
0
            const auto &replacement_code = row[12];
7429
0
            const bool replacement_is_grid_transform = row[13] == "1";
7430
0
            const bool replacement_is_known_grid = row[14] == "1";
7431
0
            if (!replacement_auth_name.empty() &&
7432
                // Ignore supersession if the replacement uses a unknown grid
7433
0
                !(replacement_is_grid_transform &&
7434
0
                  !replacement_is_known_grid) &&
7435
0
                setTransf.find(std::pair<std::string, std::string>(
7436
0
                    replacement_auth_name, replacement_code)) !=
7437
0
                    setTransf.end()) {
7438
                // Skip transformations that are superseded by others that got
7439
                // returned in the result set.
7440
0
                continue;
7441
0
            }
7442
0
        }
7443
7444
0
        bool intersecting = true;
7445
0
        try {
7446
0
            double south_lat = c_locale_stod(row[7]);
7447
0
            double west_lon = c_locale_stod(row[8]);
7448
0
            double north_lat = c_locale_stod(row[9]);
7449
0
            double east_lon = c_locale_stod(row[10]);
7450
0
            auto transf_extent = metadata::Extent::createFromBBOX(
7451
0
                west_lon, south_lat, east_lon, north_lat);
7452
7453
0
            for (const auto &extent :
7454
0
                 {intersectingExtent1, intersectingExtent2}) {
7455
0
                if (extent) {
7456
0
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
7457
0
                        intersecting = false;
7458
0
                        break;
7459
0
                    }
7460
0
                }
7461
0
            }
7462
0
        } catch (const std::exception &) {
7463
0
        }
7464
7465
0
        intersectingTransformations[thisI] = intersecting;
7466
0
        if (intersecting)
7467
0
            hasIntersectingTransformations = true;
7468
0
    }
7469
7470
    // If there are intersecting transformations, then only report those ones
7471
    // If there are no intersecting transformations, report all of them
7472
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
7473
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
7474
    // coordinates are used
7475
0
    i = 0;
7476
0
    for (const auto &row : res) {
7477
0
        size_t thisI = i;
7478
0
        ++i;
7479
0
        if ((hasIntersectingTransformations ||
7480
0
             reportOnlyIntersectingTransformations) &&
7481
0
            !intersectingTransformations[thisI]) {
7482
0
            continue;
7483
0
        }
7484
0
        if (discardSuperseded) {
7485
0
            const auto &replacement_auth_name = row[11];
7486
0
            const auto &replacement_code = row[12];
7487
0
            const bool replacement_is_grid_transform = row[13] == "1";
7488
0
            const bool replacement_is_known_grid = row[14] == "1";
7489
0
            if (!replacement_auth_name.empty() &&
7490
                // Ignore supersession if the replacement uses a unknown grid
7491
0
                !(replacement_is_grid_transform &&
7492
0
                  !replacement_is_known_grid) &&
7493
0
                setTransf.find(std::pair<std::string, std::string>(
7494
0
                    replacement_auth_name, replacement_code)) !=
7495
0
                    setTransf.end()) {
7496
                // Skip transformations that are superseded by others that got
7497
                // returned in the result set.
7498
0
                continue;
7499
0
            }
7500
0
        }
7501
7502
0
        const auto &source_crs_auth_name = row[0];
7503
0
        const auto &source_crs_code = row[1];
7504
0
        const auto &target_crs_auth_name = row[2];
7505
0
        const auto &target_crs_code = row[3];
7506
0
        const auto &auth_name = row[4];
7507
0
        const auto &code = row[5];
7508
0
        const auto &table_name = row[6];
7509
0
        try {
7510
0
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
7511
0
                code, true, usePROJAlternativeGridNames, table_name);
7512
0
            if (tryReverseOrder &&
7513
0
                (!sourceCRSAuthName.empty()
7514
0
                     ? (source_crs_auth_name != sourceCRSAuthName ||
7515
0
                        source_crs_code != sourceCRSCode)
7516
0
                     : (target_crs_auth_name != targetCRSAuthName ||
7517
0
                        target_crs_code != targetCRSCode))) {
7518
0
                op = op->inverse();
7519
0
            }
7520
0
            if (!discardIfMissingGrid ||
7521
0
                !d->rejectOpDueToMissingGrid(op,
7522
0
                                             considerKnownGridsAsAvailable)) {
7523
0
                list.emplace_back(op);
7524
0
            }
7525
0
        } catch (const std::exception &e) {
7526
            // Mostly for debugging purposes when using an inconsistent
7527
            // database
7528
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7529
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7530
0
            } else {
7531
0
                throw;
7532
0
            }
7533
0
        }
7534
0
    }
7535
0
    d->context()->d->cache(cacheKey, list);
7536
0
    return list;
7537
0
}
7538
7539
// ---------------------------------------------------------------------------
7540
7541
//! @cond Doxygen_Suppress
7542
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
7543
                               const std::string &sourceCRSAuthName,
7544
                               const std::string &sourceCRSCode,
7545
                               const std::string &targetCRSAuthName,
7546
0
                               const std::string &targetCRSCode) {
7547
0
    auto concat =
7548
0
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
7549
0
    if (!concat) {
7550
0
        return false;
7551
0
    }
7552
0
    auto ops = concat->operations();
7553
0
    for (size_t i = 0; i + 1 < ops.size(); i++) {
7554
0
        auto targetCRS = ops[i]->targetCRS();
7555
0
        if (targetCRS) {
7556
0
            const auto &ids = targetCRS->identifiers();
7557
0
            if (ids.size() == 1 &&
7558
0
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
7559
0
                  ids[0]->code() == sourceCRSCode) ||
7560
0
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7561
0
                  ids[0]->code() == targetCRSCode))) {
7562
0
                return true;
7563
0
            }
7564
0
        }
7565
0
    }
7566
0
    return false;
7567
0
}
7568
//! @endcond
7569
7570
// ---------------------------------------------------------------------------
7571
7572
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7573
 * using intermediate codes.
7574
 *
7575
 * The list is ordered with preferred operations first.
7576
 *
7577
 * Deprecated operations are rejected.
7578
 *
7579
 * The method will take care of considering all potential combinations (i.e.
7580
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7581
 * call it with sourceCRS and targetCRS switched)
7582
 *
7583
 * If getAuthority() returns empty, then coordinate operations from all
7584
 * authorities are considered.
7585
 *
7586
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7587
 * @param sourceCRSCode Source CRS code allocated by authority
7588
 * sourceCRSAuthName.
7589
 * @param targetCRSAuthName Authority name of targetCRSCode
7590
 * @param targetCRSCode Source CRS code allocated by authority
7591
 * targetCRSAuthName.
7592
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7593
 * should be substituted to the official grid names.
7594
 * @param discardIfMissingGrid Whether coordinate operations that reference
7595
 * missing grids should be removed from the result set.
7596
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7597
 * as available (typically when network is enabled).
7598
 * @param discardSuperseded Whether coordinate operations that are superseded
7599
 * (but not deprecated) should be removed from the result set.
7600
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7601
 * used as potential intermediate CRS. If the list is empty, the database will
7602
 * be used to find common CRS in operations involving both the source and
7603
 * target CRS.
7604
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7605
 * object considered.
7606
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7607
 * @param allowedAuthorities One or several authority name allowed for the two
7608
 * coordinate operations that are going to be searched. When this vector is
7609
 * no empty, it overrides the authority of this object. This is useful for
7610
 * example when the coordinate operations to chain belong to two different
7611
 * allowed authorities.
7612
 * @param intersectingExtent1 Optional extent that the resulting operations
7613
 * must intersect.
7614
 * @param intersectingExtent2 Optional extent that the resulting operations
7615
 * must intersect.
7616
 * @param skipIntermediateExtentIntersection When true, skip the requirement
7617
 * that the extents of the two intermediate operations must intersect each
7618
 * other. This is useful when SourceTargetCRSExtentUse::NONE is set.
7619
 * @return list of coordinate operations
7620
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7621
 * @throw FactoryException in case of other errors.
7622
 */
7623
7624
std::vector<operation::CoordinateOperationNNPtr>
7625
AuthorityFactory::createFromCRSCodesWithIntermediates(
7626
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7627
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7628
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7629
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7630
    const std::vector<std::pair<std::string, std::string>>
7631
        &intermediateCRSAuthCodes,
7632
    ObjectType allowedIntermediateObjectType,
7633
    const std::vector<std::string> &allowedAuthorities,
7634
    const metadata::ExtentPtr &intersectingExtent1,
7635
    const metadata::ExtentPtr &intersectingExtent2,
7636
0
    bool skipIntermediateExtentIntersection) const {
7637
7638
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7639
7640
0
    if (sourceCRSAuthName == targetCRSAuthName &&
7641
0
        sourceCRSCode == targetCRSCode) {
7642
0
        return listTmp;
7643
0
    }
7644
7645
0
    const auto CheckIfHasOperations = [this](const std::string &auth_name,
7646
0
                                             const std::string &code) {
7647
0
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7648
0
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7649
0
                        "(target_crs_auth_name = ? AND target_crs_code = ?) "
7650
0
                        "LIMIT 1",
7651
0
                        {auth_name, code, auth_name, code})
7652
0
                     .empty());
7653
0
    };
7654
7655
    // If the source or target CRS are not the source or target of an operation,
7656
    // do not run the next costly requests.
7657
0
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7658
0
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7659
0
        return listTmp;
7660
0
    }
7661
7662
0
    const std::string sqlProlog(
7663
0
        discardSuperseded
7664
0
            ?
7665
7666
0
            "SELECT v1.table_name as table1, "
7667
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7668
0
            "v1.accuracy AS accuracy1, "
7669
0
            "v2.table_name as table2, "
7670
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7671
0
            "v2.accuracy as accuracy2, "
7672
0
            "a1.south_lat AS south_lat1, "
7673
0
            "a1.west_lon AS west_lon1, "
7674
0
            "a1.north_lat AS north_lat1, "
7675
0
            "a1.east_lon AS east_lon1, "
7676
0
            "a2.south_lat AS south_lat2, "
7677
0
            "a2.west_lon AS west_lon2, "
7678
0
            "a2.north_lat AS north_lat2, "
7679
0
            "a2.east_lon AS east_lon2, "
7680
0
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7681
0
            "ss1.replacement_code AS replacement_code1, "
7682
0
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7683
0
            "ss2.replacement_code AS replacement_code2 "
7684
0
            "FROM coordinate_operation_view v1 "
7685
0
            "JOIN coordinate_operation_view v2 "
7686
0
            :
7687
7688
0
            "SELECT v1.table_name as table1, "
7689
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7690
0
            "v1.accuracy AS accuracy1, "
7691
0
            "v2.table_name as table2, "
7692
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7693
0
            "v2.accuracy as accuracy2, "
7694
0
            "a1.south_lat AS south_lat1, "
7695
0
            "a1.west_lon AS west_lon1, "
7696
0
            "a1.north_lat AS north_lat1, "
7697
0
            "a1.east_lon AS east_lon1, "
7698
0
            "a2.south_lat AS south_lat2, "
7699
0
            "a2.west_lon AS west_lon2, "
7700
0
            "a2.north_lat AS north_lat2, "
7701
0
            "a2.east_lon AS east_lon2 "
7702
0
            "FROM coordinate_operation_view v1 "
7703
0
            "JOIN coordinate_operation_view v2 ");
7704
7705
0
    const char *joinSupersession =
7706
0
        "LEFT JOIN supersession ss1 ON "
7707
0
        "ss1.superseded_table_name = v1.table_name AND "
7708
0
        "ss1.superseded_auth_name = v1.auth_name AND "
7709
0
        "ss1.superseded_code = v1.code AND "
7710
0
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7711
0
        "ss1.same_source_target_crs = 1 "
7712
0
        "LEFT JOIN supersession ss2 ON "
7713
0
        "ss2.superseded_table_name = v2.table_name AND "
7714
0
        "ss2.superseded_auth_name = v2.auth_name AND "
7715
0
        "ss2.superseded_code = v2.code AND "
7716
0
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7717
0
        "ss2.same_source_target_crs = 1 ";
7718
0
    const std::string joinArea(
7719
0
        (discardSuperseded ? std::string(joinSupersession) : std::string())
7720
0
            .append("JOIN usage u1 ON "
7721
0
                    "u1.object_table_name = v1.table_name AND "
7722
0
                    "u1.object_auth_name = v1.auth_name AND "
7723
0
                    "u1.object_code = v1.code "
7724
0
                    "JOIN extent a1 "
7725
0
                    "ON a1.auth_name = u1.extent_auth_name AND "
7726
0
                    "a1.code = u1.extent_code "
7727
0
                    "JOIN usage u2 ON "
7728
0
                    "u2.object_table_name = v2.table_name AND "
7729
0
                    "u2.object_auth_name = v2.auth_name AND "
7730
0
                    "u2.object_code = v2.code "
7731
0
                    "JOIN extent a2 "
7732
0
                    "ON a2.auth_name = u2.extent_auth_name AND "
7733
0
                    "a2.code = u2.extent_code "));
7734
0
    const std::string orderBy(
7735
0
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7736
0
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7737
0
        "accuracy1 + accuracy2");
7738
7739
    // Case (source->intermediate) and (intermediate->target)
7740
0
    std::string sql(
7741
0
        sqlProlog +
7742
0
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7743
0
        "AND v1.target_crs_code = v2.source_crs_code " +
7744
0
        joinArea +
7745
0
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7746
0
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7747
0
    std::string minDate;
7748
0
    std::string criterionOnIntermediateCRS;
7749
7750
0
    const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7751
0
                               ->createCoordinateReferenceSystem(sourceCRSCode);
7752
0
    const auto targetCRS = d->createFactory(targetCRSAuthName)
7753
0
                               ->createCoordinateReferenceSystem(targetCRSCode);
7754
7755
0
    const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7756
0
                            starts_with(targetCRS->nameStr(), "ETRF");
7757
7758
0
    const bool NAD83_CSRS_to_NAD83_CSRS =
7759
0
        starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") &&
7760
0
        starts_with(targetCRS->nameStr(), "NAD83(CSRS)");
7761
7762
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7763
0
        const auto &sourceGeogCRS =
7764
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7765
0
        const auto &targetGeogCRS =
7766
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7767
0
        if (sourceGeogCRS && targetGeogCRS) {
7768
0
            const auto &sourceDatum = sourceGeogCRS->datum();
7769
0
            const auto &targetDatum = targetGeogCRS->datum();
7770
0
            if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7771
0
                targetDatum && targetDatum->publicationDate().has_value()) {
7772
0
                const auto sourceDate(
7773
0
                    sourceDatum->publicationDate()->toString());
7774
0
                const auto targetDate(
7775
0
                    targetDatum->publicationDate()->toString());
7776
0
                minDate = std::min(sourceDate, targetDate);
7777
                // Check that the datum of the intermediateCRS has a publication
7778
                // date most recent that the one of the source and the target
7779
                // CRS Except when using the usual WGS84 pivot which happens to
7780
                // have a NULL publication date.
7781
0
                criterionOnIntermediateCRS =
7782
0
                    "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7783
0
                    "JOIN geodetic_datum y "
7784
0
                    "ON "
7785
0
                    "y.auth_name = x.datum_auth_name AND "
7786
0
                    "y.code = x.datum_code "
7787
0
                    "WHERE "
7788
0
                    "x.auth_name = v1.target_crs_auth_name AND "
7789
0
                    "x.code = v1.target_crs_code AND "
7790
0
                    "x.type IN ('geographic 2D', 'geographic 3D') AND "
7791
0
                    "(y.publication_date IS NULL OR "
7792
0
                    "(y.publication_date >= '" +
7793
0
                    minDate + "'))) ";
7794
0
            }
7795
0
        }
7796
0
        if (criterionOnIntermediateCRS.empty()) {
7797
0
            criterionOnIntermediateCRS =
7798
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7799
0
                "x.auth_name = v1.target_crs_auth_name AND "
7800
0
                "x.code = v1.target_crs_code AND "
7801
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7802
0
        }
7803
0
        sql += criterionOnIntermediateCRS;
7804
0
    }
7805
0
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7806
0
                               targetCRSAuthName, targetCRSCode};
7807
0
    std::string additionalWhere(
7808
0
        skipIntermediateExtentIntersection
7809
0
            ? "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7810
0
            : "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7811
0
              "AND intersects_bbox(south_lat1, west_lon1, north_lat1, "
7812
0
              "east_lon1, south_lat2, west_lon2, north_lat2, "
7813
0
              "east_lon2) = 1 ");
7814
0
    if (!allowedAuthorities.empty()) {
7815
0
        additionalWhere += "AND v1.auth_name IN (";
7816
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7817
0
            if (i > 0)
7818
0
                additionalWhere += ',';
7819
0
            additionalWhere += '?';
7820
0
        }
7821
0
        additionalWhere += ") AND v2.auth_name IN (";
7822
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7823
0
            if (i > 0)
7824
0
                additionalWhere += ',';
7825
0
            additionalWhere += '?';
7826
0
        }
7827
0
        additionalWhere += ')';
7828
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7829
0
            params.emplace_back(allowedAuthority);
7830
0
        }
7831
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7832
0
            params.emplace_back(allowedAuthority);
7833
0
        }
7834
0
    }
7835
0
    if (d->hasAuthorityRestriction()) {
7836
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7837
0
        params.emplace_back(d->authority());
7838
0
        params.emplace_back(d->authority());
7839
0
    }
7840
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7841
0
        if (extent) {
7842
0
            const auto &geogExtent = extent->geographicElements();
7843
0
            if (geogExtent.size() == 1) {
7844
0
                auto bbox =
7845
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7846
0
                        geogExtent[0].get());
7847
0
                if (bbox) {
7848
0
                    const double south_lat = bbox->southBoundLatitude();
7849
0
                    const double west_lon = bbox->westBoundLongitude();
7850
0
                    const double north_lat = bbox->northBoundLatitude();
7851
0
                    const double east_lon = bbox->eastBoundLongitude();
7852
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7853
0
                        north_lat != 90.0 || east_lon != 180.0) {
7854
0
                        additionalWhere +=
7855
0
                            "AND intersects_bbox(south_lat1, "
7856
0
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7857
0
                            "intersects_bbox(south_lat2, west_lon2, "
7858
0
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7859
0
                        params.emplace_back(south_lat);
7860
0
                        params.emplace_back(west_lon);
7861
0
                        params.emplace_back(north_lat);
7862
0
                        params.emplace_back(east_lon);
7863
0
                        params.emplace_back(south_lat);
7864
0
                        params.emplace_back(west_lon);
7865
0
                        params.emplace_back(north_lat);
7866
0
                        params.emplace_back(east_lon);
7867
0
                    }
7868
0
                }
7869
0
            }
7870
0
        }
7871
0
    }
7872
7873
0
    const auto buildIntermediateWhere =
7874
0
        [&intermediateCRSAuthCodes](const std::string &first_field,
7875
0
                                    const std::string &second_field) {
7876
0
            if (intermediateCRSAuthCodes.empty()) {
7877
0
                return std::string();
7878
0
            }
7879
0
            std::string l_sql(" AND (");
7880
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7881
0
                if (i > 0) {
7882
0
                    l_sql += " OR";
7883
0
                }
7884
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7885
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7886
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7887
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7888
0
            }
7889
0
            l_sql += ')';
7890
0
            return l_sql;
7891
0
        };
7892
7893
0
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7894
0
    for (const auto &pair : intermediateCRSAuthCodes) {
7895
0
        params.emplace_back(pair.first);
7896
0
        params.emplace_back(pair.second);
7897
0
        params.emplace_back(pair.first);
7898
0
        params.emplace_back(pair.second);
7899
0
    }
7900
0
    auto res =
7901
0
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7902
7903
0
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7904
0
        std::set<std::pair<std::string, std::string>> setTransf1;
7905
0
        std::set<std::pair<std::string, std::string>> setTransf2;
7906
0
        for (const auto &row : resultSet) {
7907
            // table1
7908
0
            const auto &auth_name1 = row[1];
7909
0
            const auto &code1 = row[2];
7910
            // accuracy1
7911
            // table2
7912
0
            const auto &auth_name2 = row[5];
7913
0
            const auto &code2 = row[6];
7914
0
            setTransf1.insert(
7915
0
                std::pair<std::string, std::string>(auth_name1, code1));
7916
0
            setTransf2.insert(
7917
0
                std::pair<std::string, std::string>(auth_name2, code2));
7918
0
        }
7919
0
        SQLResultSet filteredResultSet;
7920
0
        for (const auto &row : resultSet) {
7921
0
            const auto &replacement_auth_name1 = row[16];
7922
0
            const auto &replacement_code1 = row[17];
7923
0
            const auto &replacement_auth_name2 = row[18];
7924
0
            const auto &replacement_code2 = row[19];
7925
0
            if (!replacement_auth_name1.empty() &&
7926
0
                setTransf1.find(std::pair<std::string, std::string>(
7927
0
                    replacement_auth_name1, replacement_code1)) !=
7928
0
                    setTransf1.end()) {
7929
                // Skip transformations that are superseded by others that got
7930
                // returned in the result set.
7931
0
                continue;
7932
0
            }
7933
0
            if (!replacement_auth_name2.empty() &&
7934
0
                setTransf2.find(std::pair<std::string, std::string>(
7935
0
                    replacement_auth_name2, replacement_code2)) !=
7936
0
                    setTransf2.end()) {
7937
                // Skip transformations that are superseded by others that got
7938
                // returned in the result set.
7939
0
                continue;
7940
0
            }
7941
0
            filteredResultSet.emplace_back(row);
7942
0
        }
7943
0
        return filteredResultSet;
7944
0
    };
7945
7946
0
    if (discardSuperseded) {
7947
0
        res = filterOutSuperseded(std::move(res));
7948
0
    }
7949
7950
0
    const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS,
7951
0
                             &targetCRS](const crs::CRSPtr &intermediateCRS) {
7952
        // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or
7953
        // ITRF>2014
7954
0
        if (ETRFtoETRF && intermediateCRS &&
7955
0
            starts_with(intermediateCRS->nameStr(), "ITRF")) {
7956
0
            const auto normalizeDate = [](int v) {
7957
0
                return (v >= 80 && v <= 99) ? v + 1900 : v;
7958
0
            };
7959
0
            const int srcDate = normalizeDate(
7960
0
                atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7961
0
            const int tgtDate = normalizeDate(
7962
0
                atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7963
0
            const int intermDate = normalizeDate(
7964
0
                atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7965
0
            if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7966
0
                if (intermDate < std::min(srcDate, tgtDate) ||
7967
0
                    intermDate > std::max(srcDate, tgtDate)) {
7968
0
                    return false;
7969
0
                }
7970
0
            }
7971
0
        }
7972
7973
        // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through
7974
        // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464
7975
0
        if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS &&
7976
0
            (intermediateCRS->nameStr() == "NAD83" ||
7977
0
             intermediateCRS->nameStr() == "WGS 84")) {
7978
0
            return false;
7979
0
        }
7980
7981
0
        return true;
7982
0
    };
7983
7984
0
    for (const auto &row : res) {
7985
0
        const auto &table1 = row[0];
7986
0
        const auto &auth_name1 = row[1];
7987
0
        const auto &code1 = row[2];
7988
        // const auto &accuracy1 = row[3];
7989
0
        const auto &table2 = row[4];
7990
0
        const auto &auth_name2 = row[5];
7991
0
        const auto &code2 = row[6];
7992
        // const auto &accuracy2 = row[7];
7993
0
        try {
7994
0
            auto op1 =
7995
0
                d->createFactory(auth_name1)
7996
0
                    ->createCoordinateOperation(
7997
0
                        code1, true, usePROJAlternativeGridNames, table1);
7998
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7999
0
                                   targetCRSAuthName, targetCRSCode)) {
8000
0
                continue;
8001
0
            }
8002
0
            if (!checkPivot(op1->targetCRS())) {
8003
0
                continue;
8004
0
            }
8005
0
            auto op2 =
8006
0
                d->createFactory(auth_name2)
8007
0
                    ->createCoordinateOperation(
8008
0
                        code2, true, usePROJAlternativeGridNames, table2);
8009
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8010
0
                                   targetCRSAuthName, targetCRSCode)) {
8011
0
                continue;
8012
0
            }
8013
8014
0
            listTmp.emplace_back(
8015
0
                operation::ConcatenatedOperation::createComputeMetadata(
8016
0
                    {std::move(op1), std::move(op2)}, false));
8017
0
        } catch (const std::exception &e) {
8018
            // Mostly for debugging purposes when using an inconsistent
8019
            // database
8020
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8021
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8022
0
            } else {
8023
0
                throw;
8024
0
            }
8025
0
        }
8026
0
    }
8027
8028
    // Case (source->intermediate) and (target->intermediate)
8029
0
    sql = sqlProlog +
8030
0
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
8031
0
          "AND v1.target_crs_code = v2.target_crs_code " +
8032
0
          joinArea +
8033
0
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
8034
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8035
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8036
0
        sql += criterionOnIntermediateCRS;
8037
0
    }
8038
0
    intermediateWhere = buildIntermediateWhere("target", "target");
8039
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8040
0
    if (discardSuperseded) {
8041
0
        res = filterOutSuperseded(std::move(res));
8042
0
    }
8043
8044
0
    for (const auto &row : res) {
8045
0
        const auto &table1 = row[0];
8046
0
        const auto &auth_name1 = row[1];
8047
0
        const auto &code1 = row[2];
8048
        // const auto &accuracy1 = row[3];
8049
0
        const auto &table2 = row[4];
8050
0
        const auto &auth_name2 = row[5];
8051
0
        const auto &code2 = row[6];
8052
        // const auto &accuracy2 = row[7];
8053
0
        try {
8054
0
            auto op1 =
8055
0
                d->createFactory(auth_name1)
8056
0
                    ->createCoordinateOperation(
8057
0
                        code1, true, usePROJAlternativeGridNames, table1);
8058
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8059
0
                                   targetCRSAuthName, targetCRSCode)) {
8060
0
                continue;
8061
0
            }
8062
0
            if (!checkPivot(op1->targetCRS())) {
8063
0
                continue;
8064
0
            }
8065
0
            auto op2 =
8066
0
                d->createFactory(auth_name2)
8067
0
                    ->createCoordinateOperation(
8068
0
                        code2, true, usePROJAlternativeGridNames, table2);
8069
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8070
0
                                   targetCRSAuthName, targetCRSCode)) {
8071
0
                continue;
8072
0
            }
8073
8074
0
            listTmp.emplace_back(
8075
0
                operation::ConcatenatedOperation::createComputeMetadata(
8076
0
                    {std::move(op1), op2->inverse()}, false));
8077
0
        } catch (const std::exception &e) {
8078
            // Mostly for debugging purposes when using an inconsistent
8079
            // database
8080
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8081
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8082
0
            } else {
8083
0
                throw;
8084
0
            }
8085
0
        }
8086
0
    }
8087
8088
    // Case (intermediate->source) and (intermediate->target)
8089
0
    sql = sqlProlog +
8090
0
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
8091
0
          "AND v1.source_crs_code = v2.source_crs_code " +
8092
0
          joinArea +
8093
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8094
0
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
8095
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8096
0
        if (!minDate.empty()) {
8097
0
            criterionOnIntermediateCRS =
8098
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
8099
0
                "JOIN geodetic_datum y "
8100
0
                "ON "
8101
0
                "y.auth_name = x.datum_auth_name AND "
8102
0
                "y.code = x.datum_code "
8103
0
                "WHERE "
8104
0
                "x.auth_name = v1.source_crs_auth_name AND "
8105
0
                "x.code = v1.source_crs_code AND "
8106
0
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
8107
0
                "(y.publication_date IS NULL OR "
8108
0
                "(y.publication_date >= '" +
8109
0
                minDate + "'))) ";
8110
0
        } else {
8111
0
            criterionOnIntermediateCRS =
8112
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
8113
0
                "x.auth_name = v1.source_crs_auth_name AND "
8114
0
                "x.code = v1.source_crs_code AND "
8115
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
8116
0
        }
8117
0
        sql += criterionOnIntermediateCRS;
8118
0
    }
8119
0
    intermediateWhere = buildIntermediateWhere("source", "source");
8120
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8121
0
    if (discardSuperseded) {
8122
0
        res = filterOutSuperseded(std::move(res));
8123
0
    }
8124
0
    for (const auto &row : res) {
8125
0
        const auto &table1 = row[0];
8126
0
        const auto &auth_name1 = row[1];
8127
0
        const auto &code1 = row[2];
8128
        // const auto &accuracy1 = row[3];
8129
0
        const auto &table2 = row[4];
8130
0
        const auto &auth_name2 = row[5];
8131
0
        const auto &code2 = row[6];
8132
        // const auto &accuracy2 = row[7];
8133
0
        try {
8134
0
            auto op1 =
8135
0
                d->createFactory(auth_name1)
8136
0
                    ->createCoordinateOperation(
8137
0
                        code1, true, usePROJAlternativeGridNames, table1);
8138
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8139
0
                                   targetCRSAuthName, targetCRSCode)) {
8140
0
                continue;
8141
0
            }
8142
0
            if (!checkPivot(op1->sourceCRS())) {
8143
0
                continue;
8144
0
            }
8145
0
            auto op2 =
8146
0
                d->createFactory(auth_name2)
8147
0
                    ->createCoordinateOperation(
8148
0
                        code2, true, usePROJAlternativeGridNames, table2);
8149
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8150
0
                                   targetCRSAuthName, targetCRSCode)) {
8151
0
                continue;
8152
0
            }
8153
8154
0
            listTmp.emplace_back(
8155
0
                operation::ConcatenatedOperation::createComputeMetadata(
8156
0
                    {op1->inverse(), std::move(op2)}, false));
8157
0
        } catch (const std::exception &e) {
8158
            // Mostly for debugging purposes when using an inconsistent
8159
            // database
8160
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8161
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8162
0
            } else {
8163
0
                throw;
8164
0
            }
8165
0
        }
8166
0
    }
8167
8168
    // Case (intermediate->source) and (target->intermediate)
8169
0
    sql = sqlProlog +
8170
0
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
8171
0
          "AND v1.source_crs_code = v2.target_crs_code " +
8172
0
          joinArea +
8173
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8174
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8175
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8176
0
        sql += criterionOnIntermediateCRS;
8177
0
    }
8178
0
    intermediateWhere = buildIntermediateWhere("source", "target");
8179
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8180
0
    if (discardSuperseded) {
8181
0
        res = filterOutSuperseded(std::move(res));
8182
0
    }
8183
0
    for (const auto &row : res) {
8184
0
        const auto &table1 = row[0];
8185
0
        const auto &auth_name1 = row[1];
8186
0
        const auto &code1 = row[2];
8187
        // const auto &accuracy1 = row[3];
8188
0
        const auto &table2 = row[4];
8189
0
        const auto &auth_name2 = row[5];
8190
0
        const auto &code2 = row[6];
8191
        // const auto &accuracy2 = row[7];
8192
0
        try {
8193
0
            auto op1 =
8194
0
                d->createFactory(auth_name1)
8195
0
                    ->createCoordinateOperation(
8196
0
                        code1, true, usePROJAlternativeGridNames, table1);
8197
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8198
0
                                   targetCRSAuthName, targetCRSCode)) {
8199
0
                continue;
8200
0
            }
8201
0
            if (!checkPivot(op1->sourceCRS())) {
8202
0
                continue;
8203
0
            }
8204
0
            auto op2 =
8205
0
                d->createFactory(auth_name2)
8206
0
                    ->createCoordinateOperation(
8207
0
                        code2, true, usePROJAlternativeGridNames, table2);
8208
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8209
0
                                   targetCRSAuthName, targetCRSCode)) {
8210
0
                continue;
8211
0
            }
8212
8213
0
            listTmp.emplace_back(
8214
0
                operation::ConcatenatedOperation::createComputeMetadata(
8215
0
                    {op1->inverse(), op2->inverse()}, false));
8216
0
        } catch (const std::exception &e) {
8217
            // Mostly for debugging purposes when using an inconsistent
8218
            // database
8219
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8220
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8221
0
            } else {
8222
0
                throw;
8223
0
            }
8224
0
        }
8225
0
    }
8226
8227
0
    std::vector<operation::CoordinateOperationNNPtr> list;
8228
0
    for (const auto &op : listTmp) {
8229
0
        if (!discardIfMissingGrid ||
8230
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8231
0
            list.emplace_back(op);
8232
0
        }
8233
0
    }
8234
8235
0
    return list;
8236
0
}
8237
8238
// ---------------------------------------------------------------------------
8239
8240
//! @cond Doxygen_Suppress
8241
8242
struct TrfmInfo {
8243
    std::string situation{};
8244
    std::string table_name{};
8245
    std::string auth_name{};
8246
    std::string code{};
8247
    std::string name{};
8248
    double west = 0;
8249
    double south = 0;
8250
    double east = 0;
8251
    double north = 0;
8252
};
8253
8254
// ---------------------------------------------------------------------------
8255
8256
std::vector<operation::CoordinateOperationNNPtr>
8257
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
8258
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
8259
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
8260
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
8261
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
8262
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
8263
    const std::vector<std::string> &allowedAuthorities,
8264
    const metadata::ExtentPtr &intersectingExtent1,
8265
    const metadata::ExtentPtr &intersectingExtent2,
8266
0
    bool skipIntermediateExtentIntersection) const {
8267
8268
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
8269
8270
0
    if (sourceCRSAuthName == targetCRSAuthName &&
8271
0
        sourceCRSCode == targetCRSCode) {
8272
0
        return listTmp;
8273
0
    }
8274
0
    const auto sourceGeodCRS =
8275
0
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
8276
0
    const auto targetGeodCRS =
8277
0
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
8278
0
    if (!sourceGeodCRS || !targetGeodCRS) {
8279
0
        return listTmp;
8280
0
    }
8281
8282
0
    const bool NAD83_CSRS_to_NAD83_CSRS =
8283
0
        starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") &&
8284
0
        starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)");
8285
8286
0
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
8287
0
                                                const std::string &crsAuthName,
8288
0
                                                const std::string &crsCode) {
8289
        // Find all geodetic CRS that share the same datum as the CRS
8290
0
        SQLResultSet listCRS;
8291
8292
0
        const common::IdentifiedObject *obj = crs->datum().get();
8293
0
        if (obj == nullptr)
8294
0
            obj = crs->datumEnsemble().get();
8295
0
        assert(obj != nullptr);
8296
0
        const auto &ids = obj->identifiers();
8297
0
        std::string datumAuthName;
8298
0
        std::string datumCode;
8299
0
        if (!ids.empty()) {
8300
0
            const auto &id = ids.front();
8301
0
            datumAuthName = *(id->codeSpace());
8302
0
            datumCode = id->code();
8303
0
        } else {
8304
0
            const auto res =
8305
0
                d->run("SELECT datum_auth_name, datum_code FROM "
8306
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
8307
0
                       {crsAuthName, crsCode});
8308
0
            if (res.size() != 1) {
8309
0
                return listCRS;
8310
0
            }
8311
0
            const auto &row = res.front();
8312
0
            datumAuthName = row[0];
8313
0
            datumCode = row[1];
8314
0
        }
8315
8316
0
        listCRS =
8317
0
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
8318
0
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
8319
0
                   {datumAuthName, datumCode});
8320
0
        if (listCRS.empty()) {
8321
            // Can happen if the CRS is deprecated
8322
0
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
8323
0
        }
8324
0
        return listCRS;
8325
0
    };
8326
8327
0
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
8328
0
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
8329
0
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
8330
0
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
8331
0
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
8332
        // would happen only if we had CRS objects in the database without a
8333
        // link to a datum.
8334
0
        return listTmp;
8335
0
    }
8336
8337
0
    ListOfParams params;
8338
0
    const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS,
8339
0
                               &allowedAuthorities, &params, &listSourceCRS,
8340
0
                               &listTargetCRS](bool isSourceCRS,
8341
0
                                               bool selectOnTarget) {
8342
0
        std::string situation;
8343
0
        if (isSourceCRS)
8344
0
            situation = "src";
8345
0
        else
8346
0
            situation = "tgt";
8347
0
        if (selectOnTarget)
8348
0
            situation += "_is_tgt";
8349
0
        else
8350
0
            situation += "_is_src";
8351
0
        const std::string prefix1(selectOnTarget ? "source" : "target");
8352
0
        const std::string prefix2(selectOnTarget ? "target" : "source");
8353
0
        std::string sql("SELECT '");
8354
0
        sql += situation;
8355
0
        sql += "' as situation, v.table_name, v.auth_name, "
8356
0
               "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
8357
0
               "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
8358
0
               "FROM coordinate_operation_view v "
8359
0
               "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
8360
0
        sql += prefix1;
8361
0
        sql += "_crs_auth_name AND gcrs.code = ";
8362
0
        sql += prefix1;
8363
0
        sql += "_crs_code "
8364
8365
0
               "LEFT JOIN usage u ON "
8366
0
               "u.object_table_name = v.table_name AND "
8367
0
               "u.object_auth_name = v.auth_name AND "
8368
0
               "u.object_code = v.code "
8369
0
               "LEFT JOIN extent a "
8370
0
               "ON a.auth_name = u.extent_auth_name AND "
8371
0
               "a.code = u.extent_code "
8372
0
               "WHERE v.deprecated = 0 AND (";
8373
8374
0
        std::string cond;
8375
8376
0
        const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
8377
0
        for (const auto &row : list) {
8378
0
            if (!cond.empty())
8379
0
                cond += " OR ";
8380
0
            cond += '(';
8381
0
            cond += prefix2;
8382
0
            cond += "_crs_auth_name = ? AND ";
8383
0
            cond += prefix2;
8384
0
            cond += "_crs_code = ?)";
8385
0
            params.emplace_back(row[0]);
8386
0
            params.emplace_back(row[1]);
8387
0
        }
8388
8389
0
        sql += cond;
8390
0
        sql += ") ";
8391
8392
0
        if (!allowedAuthorities.empty()) {
8393
0
            sql += "AND v.auth_name IN (";
8394
0
            for (size_t i = 0; i < allowedAuthorities.size(); i++) {
8395
0
                if (i > 0)
8396
0
                    sql += ',';
8397
0
                sql += '?';
8398
0
            }
8399
0
            sql += ") ";
8400
0
            for (const auto &allowedAuthority : allowedAuthorities) {
8401
0
                params.emplace_back(allowedAuthority);
8402
0
            }
8403
0
        }
8404
0
        if (d->hasAuthorityRestriction()) {
8405
0
            sql += "AND v.auth_name = ? ";
8406
0
            params.emplace_back(d->authority());
8407
0
        }
8408
0
        if (NAD83_CSRS_to_NAD83_CSRS) {
8409
            // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go
8410
            // through NAD83 generic. Cf
8411
            // https://github.com/OSGeo/PROJ/issues/4464
8412
0
            sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') ";
8413
0
        }
8414
8415
0
        return sql;
8416
0
    };
8417
8418
0
    std::string sql(BuildSQLPart(true, true));
8419
0
    sql += "UNION ALL ";
8420
0
    sql += BuildSQLPart(false, true);
8421
0
    sql += "UNION ALL ";
8422
0
    sql += BuildSQLPart(true, false);
8423
0
    sql += "UNION ALL ";
8424
0
    sql += BuildSQLPart(false, false);
8425
    // fprintf(stderr, "sql : %s\n", sql.c_str());
8426
8427
    // Find all operations that have as source/target CRS a CRS that
8428
    // share the same datum as the source or targetCRS
8429
0
    const auto res = d->run(sql, params);
8430
8431
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
8432
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
8433
8434
0
    for (const auto &row : res) {
8435
0
        try {
8436
0
            TrfmInfo trfm;
8437
0
            trfm.situation = row[0];
8438
0
            trfm.table_name = row[1];
8439
0
            trfm.auth_name = row[2];
8440
0
            trfm.code = row[3];
8441
0
            trfm.name = row[4];
8442
0
            const auto &datum_auth_name = row[5];
8443
0
            const auto &datum_code = row[6];
8444
0
            trfm.west = c_locale_stod(row[7]);
8445
0
            trfm.south = c_locale_stod(row[8]);
8446
0
            trfm.east = c_locale_stod(row[9]);
8447
0
            trfm.north = c_locale_stod(row[10]);
8448
0
            const std::string key =
8449
0
                std::string(datum_auth_name).append(":").append(datum_code);
8450
0
            if (trfm.situation == "src_is_tgt" ||
8451
0
                trfm.situation == "src_is_src")
8452
0
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
8453
0
            else
8454
0
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
8455
0
        } catch (const std::exception &) {
8456
0
        }
8457
0
    }
8458
8459
0
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
8460
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
8461
0
        if (extent) {
8462
0
            const auto &geogExtent = extent->geographicElements();
8463
0
            if (geogExtent.size() == 1) {
8464
0
                auto bbox =
8465
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
8466
0
                        geogExtent[0].get());
8467
0
                if (bbox) {
8468
0
                    const double south_lat = bbox->southBoundLatitude();
8469
0
                    const double west_lon = bbox->westBoundLongitude();
8470
0
                    const double north_lat = bbox->northBoundLatitude();
8471
0
                    const double east_lon = bbox->eastBoundLongitude();
8472
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
8473
0
                        north_lat != 90.0 || east_lon != 180.0) {
8474
0
                        extraBbox.emplace_back(bbox);
8475
0
                    }
8476
0
                }
8477
0
            }
8478
0
        }
8479
0
    }
8480
8481
0
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
8482
0
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
8483
0
    std::map<std::string, TrfmInfo> setOfTransformations;
8484
8485
0
    const auto MakeKey = [](const TrfmInfo &trfm) {
8486
0
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
8487
0
    };
8488
8489
    // Find transformations that share a pivot datum, and do bbox filtering
8490
0
    for (const auto &kvSource : mapIntermDatumOfSource) {
8491
0
        const auto &listTrmfSource = kvSource.second;
8492
0
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
8493
0
        if (iter == mapIntermDatumOfTarget.end())
8494
0
            continue;
8495
8496
0
        const auto &listTrfmTarget = iter->second;
8497
0
        for (const auto &trfmSource : listTrmfSource) {
8498
0
            auto bbox1 = metadata::GeographicBoundingBox::create(
8499
0
                trfmSource.west, trfmSource.south, trfmSource.east,
8500
0
                trfmSource.north);
8501
0
            bool okBbox1 = true;
8502
0
            for (const auto bbox : extraBbox)
8503
0
                okBbox1 &= bbox->intersects(bbox1);
8504
0
            if (!okBbox1)
8505
0
                continue;
8506
8507
0
            const std::string key1 = MakeKey(trfmSource);
8508
8509
0
            for (const auto &trfmTarget : listTrfmTarget) {
8510
0
                auto bbox2 = metadata::GeographicBoundingBox::create(
8511
0
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
8512
0
                    trfmTarget.north);
8513
0
                if (!skipIntermediateExtentIntersection &&
8514
0
                    !bbox1->intersects(bbox2))
8515
0
                    continue;
8516
0
                bool okBbox2 = true;
8517
0
                for (const auto bbox : extraBbox)
8518
0
                    okBbox2 &= bbox->intersects(bbox2);
8519
0
                if (!okBbox2)
8520
0
                    continue;
8521
8522
0
                operation::CoordinateOperationPtr op1;
8523
0
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
8524
0
                    auto op1NN = d->createFactory(trfmSource.auth_name)
8525
0
                                     ->createCoordinateOperation(
8526
0
                                         trfmSource.code, true,
8527
0
                                         usePROJAlternativeGridNames,
8528
0
                                         trfmSource.table_name);
8529
0
                    op1 = op1NN.as_nullable();
8530
0
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
8531
0
                                           sourceCRSCode, targetCRSAuthName,
8532
0
                                           targetCRSCode)) {
8533
0
                        op1.reset();
8534
0
                    }
8535
0
                    oMapTrfmKeyToOp[key1] = op1;
8536
0
                } else {
8537
0
                    op1 = oMapTrfmKeyToOp[key1];
8538
0
                }
8539
0
                if (op1 == nullptr)
8540
0
                    continue;
8541
8542
0
                const std::string key2 = MakeKey(trfmTarget);
8543
8544
0
                operation::CoordinateOperationPtr op2;
8545
0
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
8546
0
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
8547
0
                                     ->createCoordinateOperation(
8548
0
                                         trfmTarget.code, true,
8549
0
                                         usePROJAlternativeGridNames,
8550
0
                                         trfmTarget.table_name);
8551
0
                    op2 = op2NN.as_nullable();
8552
0
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
8553
0
                                           sourceCRSCode, targetCRSAuthName,
8554
0
                                           targetCRSCode)) {
8555
0
                        op2.reset();
8556
0
                    }
8557
0
                    oMapTrfmKeyToOp[key2] = op2;
8558
0
                } else {
8559
0
                    op2 = oMapTrfmKeyToOp[key2];
8560
0
                }
8561
0
                if (op2 == nullptr)
8562
0
                    continue;
8563
8564
0
                candidates.emplace_back(
8565
0
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
8566
0
                setOfTransformations[key1] = trfmSource;
8567
0
                setOfTransformations[key2] = trfmTarget;
8568
0
            }
8569
0
        }
8570
0
    }
8571
8572
0
    std::set<std::string> setSuperseded;
8573
0
    if (discardSuperseded && !setOfTransformations.empty()) {
8574
0
        std::string findSupersededSql(
8575
0
            "SELECT superseded_table_name, "
8576
0
            "superseded_auth_name, superseded_code, "
8577
0
            "replacement_auth_name, replacement_code "
8578
0
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
8579
0
        bool findSupersededFirstWhere = true;
8580
0
        ListOfParams findSupersededParams;
8581
8582
0
        const auto keyMapSupersession = [](const std::string &table_name,
8583
0
                                           const std::string &auth_name,
8584
0
                                           const std::string &code) {
8585
0
            return table_name + auth_name + code;
8586
0
        };
8587
8588
0
        std::set<std::pair<std::string, std::string>> setTransf;
8589
0
        for (const auto &kv : setOfTransformations) {
8590
0
            const auto &table = kv.second.table_name;
8591
0
            const auto &auth_name = kv.second.auth_name;
8592
0
            const auto &code = kv.second.code;
8593
8594
0
            if (!findSupersededFirstWhere)
8595
0
                findSupersededSql += " OR ";
8596
0
            findSupersededFirstWhere = false;
8597
0
            findSupersededSql +=
8598
0
                "(superseded_table_name = ? AND replacement_table_name = "
8599
0
                "superseded_table_name AND superseded_auth_name = ? AND "
8600
0
                "superseded_code = ?)";
8601
0
            findSupersededParams.push_back(table);
8602
0
            findSupersededParams.push_back(auth_name);
8603
0
            findSupersededParams.push_back(code);
8604
8605
0
            setTransf.insert(
8606
0
                std::pair<std::string, std::string>(auth_name, code));
8607
0
        }
8608
0
        findSupersededSql += ')';
8609
8610
0
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
8611
0
            mapSupersession;
8612
8613
0
        const auto resSuperseded =
8614
0
            d->run(findSupersededSql, findSupersededParams);
8615
0
        for (const auto &row : resSuperseded) {
8616
0
            const auto &superseded_table_name = row[0];
8617
0
            const auto &superseded_auth_name = row[1];
8618
0
            const auto &superseded_code = row[2];
8619
0
            const auto &replacement_auth_name = row[3];
8620
0
            const auto &replacement_code = row[4];
8621
0
            mapSupersession[keyMapSupersession(superseded_table_name,
8622
0
                                               superseded_auth_name,
8623
0
                                               superseded_code)]
8624
0
                .push_back(std::pair<std::string, std::string>(
8625
0
                    replacement_auth_name, replacement_code));
8626
0
        }
8627
8628
0
        for (const auto &kv : setOfTransformations) {
8629
0
            const auto &table = kv.second.table_name;
8630
0
            const auto &auth_name = kv.second.auth_name;
8631
0
            const auto &code = kv.second.code;
8632
8633
0
            const auto iter = mapSupersession.find(
8634
0
                keyMapSupersession(table, auth_name, code));
8635
0
            if (iter != mapSupersession.end()) {
8636
0
                bool foundReplacement = false;
8637
0
                for (const auto &replacement : iter->second) {
8638
0
                    const auto &replacement_auth_name = replacement.first;
8639
0
                    const auto &replacement_code = replacement.second;
8640
0
                    if (setTransf.find(std::pair<std::string, std::string>(
8641
0
                            replacement_auth_name, replacement_code)) !=
8642
0
                        setTransf.end()) {
8643
                        // Skip transformations that are superseded by others
8644
                        // that got
8645
                        // returned in the result set.
8646
0
                        foundReplacement = true;
8647
0
                        break;
8648
0
                    }
8649
0
                }
8650
0
                if (foundReplacement) {
8651
0
                    setSuperseded.insert(kv.first);
8652
0
                }
8653
0
            }
8654
0
        }
8655
0
    }
8656
8657
0
    std::string sourceDatumPubDate;
8658
0
    const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context());
8659
0
    if (sourceDatum->publicationDate().has_value()) {
8660
0
        sourceDatumPubDate = sourceDatum->publicationDate()->toString();
8661
0
    }
8662
8663
0
    std::string targetDatumPubDate;
8664
0
    const auto targetDatum = targetGeodCRS->datumNonNull(d->context());
8665
0
    if (targetDatum->publicationDate().has_value()) {
8666
0
        targetDatumPubDate = targetDatum->publicationDate()->toString();
8667
0
    }
8668
8669
0
    const std::string mostAncientDatumPubDate =
8670
0
        (!targetDatumPubDate.empty() &&
8671
0
         (sourceDatumPubDate.empty() ||
8672
0
          targetDatumPubDate < sourceDatumPubDate))
8673
0
            ? targetDatumPubDate
8674
0
            : sourceDatumPubDate;
8675
8676
0
    auto opFactory = operation::CoordinateOperationFactory::create();
8677
0
    for (const auto &pair : candidates) {
8678
0
        const auto &trfmSource = pair.first;
8679
0
        const auto &trfmTarget = pair.second;
8680
0
        const std::string key1 = MakeKey(trfmSource);
8681
0
        const std::string key2 = MakeKey(trfmTarget);
8682
0
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8683
0
            setSuperseded.find(key2) != setSuperseded.end()) {
8684
0
            continue;
8685
0
        }
8686
0
        auto op1 = oMapTrfmKeyToOp[key1];
8687
0
        auto op2 = oMapTrfmKeyToOp[key2];
8688
0
        auto op1NN = NN_NO_CHECK(op1);
8689
0
        auto op2NN = NN_NO_CHECK(op2);
8690
0
        if (trfmSource.situation == "src_is_tgt")
8691
0
            op1NN = op1NN->inverse();
8692
0
        if (trfmTarget.situation == "tgt_is_src")
8693
0
            op2NN = op2NN->inverse();
8694
8695
0
        const auto &op1Source = op1NN->sourceCRS();
8696
0
        const auto &op1Target = op1NN->targetCRS();
8697
0
        const auto &op2Source = op2NN->sourceCRS();
8698
0
        const auto &op2Target = op2NN->targetCRS();
8699
0
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8700
0
            continue;
8701
0
        }
8702
8703
        // Skip operations using a datum that is older than the source or
8704
        // target datum (e.g to avoid ED50 to WGS84 to go through NTF)
8705
0
        if (!mostAncientDatumPubDate.empty()) {
8706
0
            const auto isOlderCRS = [this, &mostAncientDatumPubDate](
8707
0
                                        const crs::CRSPtr &crs) {
8708
0
                const auto geogCRS =
8709
0
                    dynamic_cast<const crs::GeodeticCRS *>(crs.get());
8710
0
                if (geogCRS) {
8711
0
                    const auto datum = geogCRS->datumNonNull(d->context());
8712
                    // Hum, theoretically we'd want to check
8713
                    // datum->publicationDate()->toString() <
8714
                    // mostAncientDatumPubDate but that would exclude doing
8715
                    // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008,
8716
                    // since IG05/12 Intermediate CRS has been published later
8717
                    // than ITRF2008. So use a cut of date for ancient vs
8718
                    // "modern" era.
8719
0
                    constexpr const char *CUT_OFF_DATE = "1900-01-01";
8720
0
                    if (datum->publicationDate().has_value() &&
8721
0
                        datum->publicationDate()->toString() < CUT_OFF_DATE &&
8722
0
                        mostAncientDatumPubDate > CUT_OFF_DATE) {
8723
0
                        return true;
8724
0
                    }
8725
0
                }
8726
0
                return false;
8727
0
            };
8728
8729
0
            if (isOlderCRS(op1Source) || isOlderCRS(op1Target) ||
8730
0
                isOlderCRS(op2Source) || isOlderCRS(op2Target))
8731
0
                continue;
8732
0
        }
8733
8734
0
        std::vector<operation::CoordinateOperationNNPtr> steps;
8735
8736
0
        if (!sourceCRS->isEquivalentTo(
8737
0
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8738
0
            auto opFirst =
8739
0
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8740
0
            assert(opFirst);
8741
0
            steps.emplace_back(NN_NO_CHECK(opFirst));
8742
0
        }
8743
8744
0
        steps.emplace_back(op1NN);
8745
8746
0
        if (!op1Target->isEquivalentTo(
8747
0
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8748
0
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8749
0
                                                       NN_NO_CHECK(op2Source));
8750
0
            assert(opMiddle);
8751
0
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8752
0
        }
8753
8754
0
        steps.emplace_back(op2NN);
8755
8756
0
        if (!op2Target->isEquivalentTo(
8757
0
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8758
0
            auto opLast =
8759
0
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8760
0
            assert(opLast);
8761
0
            steps.emplace_back(NN_NO_CHECK(opLast));
8762
0
        }
8763
8764
0
        listTmp.emplace_back(
8765
0
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8766
0
                                                                    false));
8767
0
    }
8768
8769
0
    std::vector<operation::CoordinateOperationNNPtr> list;
8770
0
    for (const auto &op : listTmp) {
8771
0
        if (!discardIfMissingGrid ||
8772
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8773
0
            list.emplace_back(op);
8774
0
        }
8775
0
    }
8776
8777
0
    return list;
8778
0
}
8779
8780
//! @endcond
8781
8782
// ---------------------------------------------------------------------------
8783
8784
/** \brief Returns the authority name associated to this factory.
8785
 * @return name.
8786
 */
8787
0
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8788
0
    return d->authority();
8789
0
}
8790
8791
// ---------------------------------------------------------------------------
8792
8793
/** \brief Returns the set of authority codes of the given object type.
8794
 *
8795
 * @param type Object type.
8796
 * @param allowDeprecated whether we should return deprecated objects as well.
8797
 * @return the set of authority codes for spatial reference objects of the given
8798
 * type
8799
 * @throw FactoryException in case of error.
8800
 */
8801
std::set<std::string>
8802
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8803
0
                                    bool allowDeprecated) const {
8804
0
    std::string sql;
8805
0
    switch (type) {
8806
0
    case ObjectType::PRIME_MERIDIAN:
8807
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8808
0
        break;
8809
0
    case ObjectType::ELLIPSOID:
8810
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8811
0
        break;
8812
0
    case ObjectType::DATUM:
8813
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8814
0
              "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND ";
8815
0
        break;
8816
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8817
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8818
0
        break;
8819
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8820
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8821
0
              "frame_reference_epoch IS NOT NULL AND ";
8822
0
        break;
8823
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8824
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8825
0
        break;
8826
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8827
0
        sql = "SELECT code FROM vertical_datum WHERE "
8828
0
              "frame_reference_epoch IS NOT NULL AND ";
8829
0
        break;
8830
0
    case ObjectType::ENGINEERING_DATUM:
8831
0
        sql = "SELECT code FROM engineering_datum WHERE ";
8832
0
        break;
8833
0
    case ObjectType::CRS:
8834
0
        sql = "SELECT code FROM crs_view WHERE ";
8835
0
        break;
8836
0
    case ObjectType::GEODETIC_CRS:
8837
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8838
0
        break;
8839
0
    case ObjectType::GEOCENTRIC_CRS:
8840
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8841
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8842
0
        break;
8843
0
    case ObjectType::GEOGRAPHIC_CRS:
8844
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8845
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8846
0
        break;
8847
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8848
0
        sql =
8849
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8850
0
            " AND ";
8851
0
        break;
8852
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8853
0
        sql =
8854
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8855
0
            " AND ";
8856
0
        break;
8857
0
    case ObjectType::VERTICAL_CRS:
8858
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8859
0
        break;
8860
0
    case ObjectType::PROJECTED_CRS:
8861
0
        sql = "SELECT code FROM projected_crs WHERE ";
8862
0
        break;
8863
0
    case ObjectType::COMPOUND_CRS:
8864
0
        sql = "SELECT code FROM compound_crs WHERE ";
8865
0
        break;
8866
0
    case ObjectType::ENGINEERING_CRS:
8867
0
        sql = "SELECT code FROM engineering_crs WHERE ";
8868
0
        break;
8869
0
    case ObjectType::DERIVED_PROJECTED_CRS:
8870
0
        sql = "SELECT code FROM derived_projected_crs WHERE ";
8871
0
        break;
8872
0
    case ObjectType::COORDINATE_OPERATION:
8873
0
        sql =
8874
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8875
0
        break;
8876
0
    case ObjectType::CONVERSION:
8877
0
        sql = "SELECT code FROM conversion WHERE ";
8878
0
        break;
8879
0
    case ObjectType::TRANSFORMATION:
8880
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8881
0
              "'concatenated_operation' AND ";
8882
0
        break;
8883
0
    case ObjectType::CONCATENATED_OPERATION:
8884
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8885
0
        break;
8886
0
    case ObjectType::DATUM_ENSEMBLE:
8887
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8888
0
              "('geodetic_datum', 'vertical_datum') AND "
8889
0
              "type = 'ensemble' AND ";
8890
0
        break;
8891
0
    }
8892
8893
0
    sql += "auth_name = ?";
8894
0
    if (!allowDeprecated) {
8895
0
        sql += " AND deprecated = 0";
8896
0
    }
8897
8898
0
    auto res = d->run(sql, {d->authority()});
8899
0
    std::set<std::string> set;
8900
0
    for (const auto &row : res) {
8901
0
        set.insert(row[0]);
8902
0
    }
8903
0
    return set;
8904
0
}
8905
8906
// ---------------------------------------------------------------------------
8907
8908
/** \brief Gets a description of the object corresponding to a code.
8909
 *
8910
 * \note In case of several objects of different types with the same code,
8911
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8912
 * will be selected.
8913
 *
8914
 * @param code Object code allocated by authority. (e.g. "4326")
8915
 * @return description.
8916
 * @throw NoSuchAuthorityCodeException if there is no matching object.
8917
 * @throw FactoryException in case of other errors.
8918
 */
8919
std::string
8920
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8921
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8922
0
               "AND code = ? ORDER BY table_name";
8923
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8924
0
    if (sqlRes.empty()) {
8925
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8926
0
                                           code);
8927
0
    }
8928
0
    std::string text;
8929
0
    for (const auto &row : sqlRes) {
8930
0
        const auto &tableName = row[1];
8931
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8932
0
            tableName == "vertical_crs" || tableName == "compound_crs" ||
8933
0
            tableName == "engineering_crs" ||
8934
0
            tableName == "derived_projected_crs") {
8935
0
            return row[0];
8936
0
        } else if (text.empty()) {
8937
0
            text = row[0];
8938
0
        }
8939
0
    }
8940
0
    return text;
8941
0
}
8942
8943
// ---------------------------------------------------------------------------
8944
8945
/** \brief Return a list of information on CRS objects
8946
 *
8947
 * This is functionally equivalent of listing the codes from an authority,
8948
 * instantiating
8949
 * a CRS object for each of them and getting the information from this CRS
8950
 * object, but this implementation has much less overhead.
8951
 *
8952
 * @throw FactoryException in case of error.
8953
 */
8954
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8955
8956
0
    const auto getSqlArea = [](const char *table_name) {
8957
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8958
0
        sql += table_name;
8959
0
        sql += "' AND "
8960
0
               "u.object_auth_name = c.auth_name AND "
8961
0
               "u.object_code = c.code "
8962
0
               "LEFT JOIN extent a "
8963
0
               "ON a.auth_name = u.extent_auth_name AND "
8964
0
               "a.code = u.extent_code ";
8965
0
        return sql;
8966
0
    };
8967
8968
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8969
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8970
0
        sql += crs_alias;
8971
0
        sql += ".datum_auth_name AND gd.code = ";
8972
0
        sql += crs_alias;
8973
0
        sql += ".datum_code "
8974
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8975
0
               "AND e.code = gd.ellipsoid_code "
8976
0
               "LEFT JOIN celestial_body cb ON "
8977
0
               "cb.auth_name = e.celestial_body_auth_name "
8978
0
               "AND cb.code = e.celestial_body_code ";
8979
0
        return sql;
8980
0
    };
8981
8982
0
    std::string sql = "SELECT * FROM ("
8983
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8984
0
                      "c.deprecated, "
8985
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8986
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8987
0
    sql += getSqlArea("geodetic_crs");
8988
0
    sql += getJoinCelestialBody("c");
8989
0
    ListOfParams params;
8990
0
    if (d->hasAuthorityRestriction()) {
8991
0
        sql += "WHERE c.auth_name = ? ";
8992
0
        params.emplace_back(d->authority());
8993
0
    }
8994
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8995
0
           "c.deprecated, "
8996
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8997
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8998
0
           "projected_crs c "
8999
0
           "LEFT JOIN conversion_table conv ON "
9000
0
           "c.conversion_auth_name = conv.auth_name AND "
9001
0
           "c.conversion_code = conv.code "
9002
0
           "LEFT JOIN conversion_method cm ON "
9003
0
           "conv.method_auth_name = cm.auth_name AND "
9004
0
           "conv.method_code = cm.code "
9005
0
           "LEFT JOIN geodetic_crs gcrs ON "
9006
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
9007
0
           "AND gcrs.code = c.geodetic_crs_code ";
9008
0
    sql += getSqlArea("projected_crs");
9009
0
    sql += getJoinCelestialBody("gcrs");
9010
0
    if (d->hasAuthorityRestriction()) {
9011
0
        sql += "WHERE c.auth_name = ? ";
9012
0
        params.emplace_back(d->authority());
9013
0
    }
9014
    // FIXME: we can't handle non-EARTH vertical CRS for now
9015
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
9016
0
           "c.deprecated, "
9017
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9018
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
9019
0
    sql += getSqlArea("vertical_crs");
9020
0
    if (d->hasAuthorityRestriction()) {
9021
0
        sql += "WHERE c.auth_name = ? ";
9022
0
        params.emplace_back(d->authority());
9023
0
    }
9024
    // FIXME: we can't handle non-EARTH compound CRS for now
9025
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
9026
0
           "c.deprecated, "
9027
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9028
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
9029
0
    sql += getSqlArea("compound_crs");
9030
0
    if (d->hasAuthorityRestriction()) {
9031
0
        sql += "WHERE c.auth_name = ? ";
9032
0
        params.emplace_back(d->authority());
9033
0
    }
9034
    // FIXME: we can't handle non-EARTH compound CRS for now
9035
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', "
9036
0
           "c.deprecated, "
9037
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9038
0
           "a.description, NULL, 'Earth' FROM engineering_crs c ";
9039
0
    sql += getSqlArea("engineering_crs");
9040
0
    if (d->hasAuthorityRestriction()) {
9041
0
        sql += "WHERE c.auth_name = ? ";
9042
0
        params.emplace_back(d->authority());
9043
0
    }
9044
    // FIXME: we can't handle non-EARTH compound CRS for now
9045
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'derived projected', "
9046
0
           "c.deprecated, "
9047
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9048
0
           "a.description, NULL, 'Earth' FROM derived_projected_crs c ";
9049
0
    sql += getSqlArea("derived_projected_crs");
9050
0
    if (d->hasAuthorityRestriction()) {
9051
0
        sql += "WHERE c.auth_name = ? ";
9052
0
        params.emplace_back(d->authority());
9053
0
    }
9054
0
    sql += ") r ORDER BY auth_name, code";
9055
0
    auto sqlRes = d->run(sql, params);
9056
0
    std::list<AuthorityFactory::CRSInfo> res;
9057
0
    for (const auto &row : sqlRes) {
9058
0
        AuthorityFactory::CRSInfo info;
9059
0
        info.authName = row[0];
9060
0
        info.code = row[1];
9061
0
        info.name = row[2];
9062
0
        const auto &type = row[3];
9063
0
        if (type == GEOG_2D) {
9064
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
9065
0
        } else if (type == GEOG_3D) {
9066
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
9067
0
        } else if (type == GEOCENTRIC) {
9068
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
9069
0
        } else if (type == OTHER) {
9070
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
9071
0
        } else if (type == PROJECTED) {
9072
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
9073
0
        } else if (type == VERTICAL) {
9074
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
9075
0
        } else if (type == COMPOUND) {
9076
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
9077
0
        } else if (type == ENGINEERING) {
9078
0
            info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS;
9079
0
        } else if (type == DERIVED_PROJECTED) {
9080
0
            info.type = AuthorityFactory::ObjectType::DERIVED_PROJECTED_CRS;
9081
0
        }
9082
0
        info.deprecated = row[4] == "1";
9083
0
        if (row[5].empty()) {
9084
0
            info.bbox_valid = false;
9085
0
        } else {
9086
0
            info.bbox_valid = true;
9087
0
            info.west_lon_degree = c_locale_stod(row[5]);
9088
0
            info.south_lat_degree = c_locale_stod(row[6]);
9089
0
            info.east_lon_degree = c_locale_stod(row[7]);
9090
0
            info.north_lat_degree = c_locale_stod(row[8]);
9091
0
        }
9092
0
        info.areaName = row[9];
9093
0
        info.projectionMethodName = row[10];
9094
0
        info.celestialBodyName = row[11];
9095
0
        res.emplace_back(info);
9096
0
    }
9097
0
    return res;
9098
0
}
9099
9100
// ---------------------------------------------------------------------------
9101
9102
//! @cond Doxygen_Suppress
9103
AuthorityFactory::UnitInfo::UnitInfo()
9104
0
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
9105
0
      deprecated{} {}
9106
//! @endcond
9107
9108
// ---------------------------------------------------------------------------
9109
9110
//! @cond Doxygen_Suppress
9111
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
9112
//! @endcond
9113
9114
// ---------------------------------------------------------------------------
9115
9116
/** \brief Return the list of units.
9117
 * @throw FactoryException in case of error.
9118
 *
9119
 * @since 7.1
9120
 */
9121
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
9122
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
9123
0
                      "proj_short_name, deprecated FROM unit_of_measure";
9124
0
    ListOfParams params;
9125
0
    if (d->hasAuthorityRestriction()) {
9126
0
        sql += " WHERE auth_name = ?";
9127
0
        params.emplace_back(d->authority());
9128
0
    }
9129
0
    sql += " ORDER BY auth_name, code";
9130
9131
0
    auto sqlRes = d->run(sql, params);
9132
0
    std::list<AuthorityFactory::UnitInfo> res;
9133
0
    for (const auto &row : sqlRes) {
9134
0
        AuthorityFactory::UnitInfo info;
9135
0
        info.authName = row[0];
9136
0
        info.code = row[1];
9137
0
        info.name = row[2];
9138
0
        const std::string &raw_category(row[3]);
9139
0
        if (raw_category == "length") {
9140
0
            info.category = info.name.find(" per ") != std::string::npos
9141
0
                                ? "linear_per_time"
9142
0
                                : "linear";
9143
0
        } else if (raw_category == "angle") {
9144
0
            info.category = info.name.find(" per ") != std::string::npos
9145
0
                                ? "angular_per_time"
9146
0
                                : "angular";
9147
0
        } else if (raw_category == "scale") {
9148
0
            info.category =
9149
0
                info.name.find(" per year") != std::string::npos ||
9150
0
                        info.name.find(" per second") != std::string::npos
9151
0
                    ? "scale_per_time"
9152
0
                    : "scale";
9153
0
        } else {
9154
0
            info.category = raw_category;
9155
0
        }
9156
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
9157
0
        info.projShortName = row[5];
9158
0
        info.deprecated = row[6] == "1";
9159
0
        res.emplace_back(info);
9160
0
    }
9161
0
    return res;
9162
0
}
9163
9164
// ---------------------------------------------------------------------------
9165
9166
/** \brief Return the list of celestial bodies.
9167
 * @throw FactoryException in case of error.
9168
 *
9169
 * @since 8.1
9170
 */
9171
std::list<AuthorityFactory::CelestialBodyInfo>
9172
0
AuthorityFactory::getCelestialBodyList() const {
9173
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
9174
0
    ListOfParams params;
9175
0
    if (d->hasAuthorityRestriction()) {
9176
0
        sql += " WHERE auth_name = ?";
9177
0
        params.emplace_back(d->authority());
9178
0
    }
9179
0
    sql += " ORDER BY auth_name, name";
9180
9181
0
    auto sqlRes = d->run(sql, params);
9182
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
9183
0
    for (const auto &row : sqlRes) {
9184
0
        AuthorityFactory::CelestialBodyInfo info;
9185
0
        info.authName = row[0];
9186
0
        info.name = row[1];
9187
0
        res.emplace_back(info);
9188
0
    }
9189
0
    return res;
9190
0
}
9191
9192
// ---------------------------------------------------------------------------
9193
9194
/** \brief Gets the official name from a possibly alias name.
9195
 *
9196
 * @param aliasedName Alias name.
9197
 * @param tableName Table name/category. Can help in case of ambiguities.
9198
 * Or empty otherwise.
9199
 * @param source Source of the alias. Can help in case of ambiguities.
9200
 * Or empty otherwise.
9201
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
9202
 * the alt_name column of the alias_name table should be done with using
9203
 * metadata::Identifier::isEquivalentName() rather than strict string
9204
 * comparison;
9205
 * @param outTableName Table name in which the official name has been found.
9206
 * @param outAuthName Authority name of the official name that has been found.
9207
 * @param outCode Code of the official name that has been found.
9208
 * @return official name (or empty if not found).
9209
 * @throw FactoryException in case of error.
9210
 */
9211
std::string AuthorityFactory::getOfficialNameFromAlias(
9212
    const std::string &aliasedName, const std::string &tableName,
9213
    const std::string &source, bool tryEquivalentNameSpelling,
9214
    std::string &outTableName, std::string &outAuthName,
9215
5.44k
    std::string &outCode) const {
9216
9217
5.44k
    if (tryEquivalentNameSpelling) {
9218
0
        std::string sql(
9219
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
9220
0
        ListOfParams params;
9221
0
        if (!tableName.empty()) {
9222
0
            sql += " WHERE table_name = ?";
9223
0
            params.push_back(tableName);
9224
0
        }
9225
0
        if (!source.empty()) {
9226
0
            if (!tableName.empty()) {
9227
0
                sql += " AND ";
9228
0
            } else {
9229
0
                sql += " WHERE ";
9230
0
            }
9231
0
            sql += "source = ?";
9232
0
            params.push_back(source);
9233
0
        }
9234
0
        auto res = d->run(sql, params);
9235
0
        if (res.empty()) {
9236
0
            return std::string();
9237
0
        }
9238
0
        for (const auto &row : res) {
9239
0
            const auto &alt_name = row[3];
9240
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
9241
0
                                                       aliasedName.c_str())) {
9242
0
                outTableName = row[0];
9243
0
                outAuthName = row[1];
9244
0
                outCode = row[2];
9245
0
                sql = "SELECT name FROM \"";
9246
0
                sql += replaceAll(outTableName, "\"", "\"\"");
9247
0
                sql += "\" WHERE auth_name = ? AND code = ?";
9248
0
                res = d->run(sql, {outAuthName, outCode});
9249
0
                if (res.empty()) { // shouldn't happen normally
9250
0
                    return std::string();
9251
0
                }
9252
0
                return res.front()[0];
9253
0
            }
9254
0
        }
9255
0
        return std::string();
9256
5.44k
    } else {
9257
5.44k
        std::string sql(
9258
5.44k
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
9259
5.44k
            "alt_name = ?");
9260
5.44k
        ListOfParams params{aliasedName};
9261
5.44k
        if (!tableName.empty()) {
9262
5.44k
            sql += " AND table_name = ?";
9263
5.44k
            params.push_back(tableName);
9264
5.44k
        }
9265
5.44k
        if (!source.empty()) {
9266
5.44k
            if (source == "ESRI") {
9267
5.44k
                sql += " AND source IN ('ESRI', 'ESRI_OLD')";
9268
5.44k
            } else {
9269
0
                sql += " AND source = ?";
9270
0
                params.push_back(source);
9271
0
            }
9272
5.44k
        }
9273
5.44k
        auto res = d->run(sql, params);
9274
5.44k
        if (res.empty()) {
9275
4.95k
            return std::string();
9276
4.95k
        }
9277
9278
488
        params.clear();
9279
488
        sql.clear();
9280
488
        bool first = true;
9281
488
        for (const auto &row : res) {
9282
488
            if (!first)
9283
0
                sql += " UNION ALL ";
9284
488
            first = false;
9285
488
            outTableName = row[0];
9286
488
            outAuthName = row[1];
9287
488
            outCode = row[2];
9288
488
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
9289
488
                   "FROM \"";
9290
488
            sql += replaceAll(outTableName, "\"", "\"\"");
9291
488
            sql += "\" WHERE auth_name = ? AND code = ?";
9292
488
            params.emplace_back(outTableName);
9293
488
            params.emplace_back(outAuthName);
9294
488
            params.emplace_back(outCode);
9295
488
        }
9296
488
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
9297
488
              ") x ORDER BY deprecated LIMIT 1";
9298
488
        res = d->run(sql, params);
9299
488
        if (res.empty()) { // shouldn't happen normally
9300
0
            return std::string();
9301
0
        }
9302
488
        const auto &row = res.front();
9303
488
        outTableName = row[1];
9304
488
        outAuthName = row[2];
9305
488
        outCode = row[3];
9306
488
        return row[0];
9307
488
    }
9308
5.44k
}
9309
9310
// ---------------------------------------------------------------------------
9311
9312
/** \brief Return a list of objects, identified by their name
9313
 *
9314
 * @param searchedName Searched name. Must be at least 2 character long.
9315
 * @param allowedObjectTypes List of object types into which to search. If
9316
 * empty, all object types will be searched.
9317
 * @param approximateMatch Whether approximate name identification is allowed.
9318
 * @param limitResultCount Maximum number of results to return.
9319
 * Or 0 for unlimited.
9320
 * @return list of matched objects.
9321
 * @throw FactoryException in case of error.
9322
 */
9323
std::list<common::IdentifiedObjectNNPtr>
9324
AuthorityFactory::createObjectsFromName(
9325
    const std::string &searchedName,
9326
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9327
11.3k
    size_t limitResultCount) const {
9328
11.3k
    std::list<common::IdentifiedObjectNNPtr> res;
9329
11.3k
    const auto resTmp(createObjectsFromNameEx(
9330
11.3k
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
9331
11.3k
    for (const auto &pair : resTmp) {
9332
6.93k
        res.emplace_back(pair.first);
9333
6.93k
    }
9334
11.3k
    return res;
9335
11.3k
}
9336
9337
// ---------------------------------------------------------------------------
9338
9339
//! @cond Doxygen_Suppress
9340
9341
/** \brief Return a list of objects, identifier by their name, with the name
9342
 * on which the match occurred.
9343
 *
9344
 * The name on which the match occurred might be different from the object name,
9345
 * if the match has been done on an alias name of that object.
9346
 *
9347
 * @param searchedName Searched name. Must be at least 2 character long.
9348
 * @param allowedObjectTypes List of object types into which to search. If
9349
 * empty, all object types will be searched.
9350
 * @param approximateMatch Whether approximate name identification is allowed.
9351
 * @param limitResultCount Maximum number of results to return.
9352
 * Or 0 for unlimited.
9353
 * @param useAliases Whether querying the alias_name table is allowed
9354
 * @return list of matched objects.
9355
 * @throw FactoryException in case of error.
9356
 */
9357
std::list<AuthorityFactory::PairObjectName>
9358
AuthorityFactory::createObjectsFromNameEx(
9359
    const std::string &searchedName,
9360
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9361
11.3k
    size_t limitResultCount, bool useAliases) const {
9362
11.3k
    std::string searchedNameWithoutDeprecated(searchedName);
9363
11.3k
    bool deprecated = false;
9364
11.3k
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
9365
0
        deprecated = true;
9366
0
        searchedNameWithoutDeprecated.resize(
9367
0
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
9368
0
    }
9369
9370
11.3k
    const std::string canonicalizedSearchedName(
9371
11.3k
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
9372
11.3k
    if (canonicalizedSearchedName.size() <= 1) {
9373
68
        return {};
9374
68
    }
9375
9376
11.2k
    std::string sql(
9377
11.2k
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
9378
11.2k
        "FROM (");
9379
9380
11.2k
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
9381
11.2k
                                             &searchedName]() {
9382
11.2k
        typedef std::pair<std::string, std::string> TableType;
9383
11.2k
        std::list<TableType> res;
9384
        // Hide ESRI D_ vertical datums
9385
11.2k
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
9386
11.2k
        if (allowedObjectTypes.empty()) {
9387
0
            for (const auto &tableName :
9388
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
9389
0
                  "vertical_datum", "engineering_datum", "geodetic_crs",
9390
0
                  "projected_crs", "derived_projected_crs", "vertical_crs",
9391
0
                  "compound_crs", "engineering_crs", "conversion",
9392
0
                  "helmert_transformation", "grid_transformation",
9393
0
                  "other_transformation", "concatenated_operation"}) {
9394
0
                if (!(startsWithDUnderscore &&
9395
0
                      strcmp(tableName, "vertical_datum") == 0)) {
9396
0
                    res.emplace_back(TableType(tableName, std::string()));
9397
0
                }
9398
0
            }
9399
11.2k
        } else {
9400
18.7k
            for (const auto type : allowedObjectTypes) {
9401
18.7k
                switch (type) {
9402
0
                case ObjectType::PRIME_MERIDIAN:
9403
0
                    res.emplace_back(
9404
0
                        TableType("prime_meridian", std::string()));
9405
0
                    break;
9406
6.37k
                case ObjectType::ELLIPSOID:
9407
6.37k
                    res.emplace_back(TableType("ellipsoid", std::string()));
9408
6.37k
                    break;
9409
2.49k
                case ObjectType::DATUM:
9410
2.49k
                    res.emplace_back(
9411
2.49k
                        TableType("geodetic_datum", std::string()));
9412
2.49k
                    if (!startsWithDUnderscore) {
9413
2.48k
                        res.emplace_back(
9414
2.48k
                            TableType("vertical_datum", std::string()));
9415
2.48k
                        res.emplace_back(
9416
2.48k
                            TableType("engineering_datum", std::string()));
9417
2.48k
                    }
9418
2.49k
                    break;
9419
1.83k
                case ObjectType::GEODETIC_REFERENCE_FRAME:
9420
1.83k
                    res.emplace_back(
9421
1.83k
                        TableType("geodetic_datum", std::string()));
9422
1.83k
                    break;
9423
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
9424
0
                    res.emplace_back(
9425
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
9426
0
                    break;
9427
0
                case ObjectType::VERTICAL_REFERENCE_FRAME:
9428
0
                    res.emplace_back(
9429
0
                        TableType("vertical_datum", std::string()));
9430
0
                    break;
9431
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
9432
0
                    res.emplace_back(
9433
0
                        TableType("vertical_datum", "frame_reference_epoch"));
9434
0
                    break;
9435
0
                case ObjectType::ENGINEERING_DATUM:
9436
0
                    res.emplace_back(
9437
0
                        TableType("engineering_datum", std::string()));
9438
0
                    break;
9439
2.98k
                case ObjectType::CRS:
9440
2.98k
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9441
2.98k
                    res.emplace_back(TableType("projected_crs", std::string()));
9442
2.98k
                    res.emplace_back(TableType("vertical_crs", std::string()));
9443
2.98k
                    res.emplace_back(TableType("compound_crs", std::string()));
9444
2.98k
                    res.emplace_back(
9445
2.98k
                        TableType("engineering_crs", std::string()));
9446
2.98k
                    res.emplace_back(
9447
2.98k
                        TableType("derived_projected_crs", std::string()));
9448
2.98k
                    break;
9449
0
                case ObjectType::GEODETIC_CRS:
9450
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9451
0
                    break;
9452
0
                case ObjectType::GEOCENTRIC_CRS:
9453
0
                    res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
9454
0
                    break;
9455
0
                case ObjectType::GEOGRAPHIC_CRS:
9456
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9457
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9458
0
                    break;
9459
0
                case ObjectType::GEOGRAPHIC_2D_CRS:
9460
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9461
0
                    break;
9462
43
                case ObjectType::GEOGRAPHIC_3D_CRS:
9463
43
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9464
43
                    break;
9465
0
                case ObjectType::PROJECTED_CRS:
9466
0
                    res.emplace_back(TableType("projected_crs", std::string()));
9467
0
                    break;
9468
0
                case ObjectType::DERIVED_PROJECTED_CRS:
9469
0
                    res.emplace_back(
9470
0
                        TableType("derived_projected_crs", std::string()));
9471
0
                    break;
9472
0
                case ObjectType::VERTICAL_CRS:
9473
0
                    res.emplace_back(TableType("vertical_crs", std::string()));
9474
0
                    break;
9475
0
                case ObjectType::COMPOUND_CRS:
9476
0
                    res.emplace_back(TableType("compound_crs", std::string()));
9477
0
                    break;
9478
0
                case ObjectType::ENGINEERING_CRS:
9479
0
                    res.emplace_back(
9480
0
                        TableType("engineering_crs", std::string()));
9481
0
                    break;
9482
2.49k
                case ObjectType::COORDINATE_OPERATION:
9483
2.49k
                    res.emplace_back(TableType("conversion", std::string()));
9484
2.49k
                    res.emplace_back(
9485
2.49k
                        TableType("helmert_transformation", std::string()));
9486
2.49k
                    res.emplace_back(
9487
2.49k
                        TableType("grid_transformation", std::string()));
9488
2.49k
                    res.emplace_back(
9489
2.49k
                        TableType("other_transformation", std::string()));
9490
2.49k
                    res.emplace_back(
9491
2.49k
                        TableType("concatenated_operation", std::string()));
9492
2.49k
                    break;
9493
0
                case ObjectType::CONVERSION:
9494
0
                    res.emplace_back(TableType("conversion", std::string()));
9495
0
                    break;
9496
0
                case ObjectType::TRANSFORMATION:
9497
0
                    res.emplace_back(
9498
0
                        TableType("helmert_transformation", std::string()));
9499
0
                    res.emplace_back(
9500
0
                        TableType("grid_transformation", std::string()));
9501
0
                    res.emplace_back(
9502
0
                        TableType("other_transformation", std::string()));
9503
0
                    break;
9504
0
                case ObjectType::CONCATENATED_OPERATION:
9505
0
                    res.emplace_back(
9506
0
                        TableType("concatenated_operation", std::string()));
9507
0
                    break;
9508
2.51k
                case ObjectType::DATUM_ENSEMBLE:
9509
2.51k
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
9510
2.51k
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
9511
2.51k
                    break;
9512
18.7k
                }
9513
18.7k
            }
9514
11.2k
        }
9515
11.2k
        return res;
9516
11.2k
    };
9517
9518
11.2k
    bool datumEnsembleAllowed = false;
9519
11.2k
    if (allowedObjectTypes.empty()) {
9520
0
        datumEnsembleAllowed = true;
9521
11.2k
    } else {
9522
16.2k
        for (const auto type : allowedObjectTypes) {
9523
16.2k
            if (type == ObjectType::DATUM_ENSEMBLE) {
9524
2.51k
                datumEnsembleAllowed = true;
9525
2.51k
                break;
9526
2.51k
            }
9527
16.2k
        }
9528
11.2k
    }
9529
9530
11.2k
    const auto listTableNameType = getTableAndTypeConstraints();
9531
11.2k
    bool first = true;
9532
11.2k
    ListOfParams params;
9533
51.1k
    for (const auto &tableNameTypePair : listTableNameType) {
9534
51.1k
        if (!first) {
9535
39.8k
            sql += " UNION ";
9536
39.8k
        }
9537
51.1k
        first = false;
9538
51.1k
        sql += "SELECT '";
9539
51.1k
        sql += tableNameTypePair.first;
9540
51.1k
        sql += "' AS table_name, auth_name, code, name, deprecated, "
9541
51.1k
               "0 AS is_alias FROM ";
9542
51.1k
        sql += tableNameTypePair.first;
9543
51.1k
        sql += " WHERE 1 = 1 ";
9544
51.1k
        if (!tableNameTypePair.second.empty()) {
9545
5.07k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9546
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
9547
5.07k
            } else if (tableNameTypePair.second == "ensemble") {
9548
5.03k
                sql += "AND ensemble_accuracy IS NOT NULL ";
9549
5.03k
            } else {
9550
43
                sql += "AND type = '";
9551
43
                sql += tableNameTypePair.second;
9552
43
                sql += "' ";
9553
43
            }
9554
5.07k
        }
9555
51.1k
        if (deprecated) {
9556
0
            sql += "AND deprecated = 1 ";
9557
0
        }
9558
51.1k
        if (!approximateMatch) {
9559
25.3k
            sql += "AND name = ? COLLATE NOCASE ";
9560
25.3k
            params.push_back(searchedNameWithoutDeprecated);
9561
25.3k
        }
9562
51.1k
        if (d->hasAuthorityRestriction()) {
9563
43
            sql += "AND auth_name = ? ";
9564
43
            params.emplace_back(d->authority());
9565
43
        }
9566
9567
51.1k
        if (useAliases) {
9568
51.1k
            sql += " UNION SELECT '";
9569
51.1k
            sql += tableNameTypePair.first;
9570
51.1k
            sql += "' AS table_name, "
9571
51.1k
                   "ov.auth_name AS auth_name, "
9572
51.1k
                   "ov.code AS code, a.alt_name AS name, "
9573
51.1k
                   "ov.deprecated AS deprecated, 1 as is_alias FROM ";
9574
51.1k
            sql += tableNameTypePair.first;
9575
51.1k
            sql += " ov "
9576
51.1k
                   "JOIN alias_name a ON "
9577
51.1k
                   "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
9578
51.1k
                   "a.source != 'EPSG_OLD' AND a.table_name = '";
9579
51.1k
            sql += tableNameTypePair.first;
9580
51.1k
            sql += "' ";
9581
51.1k
            if (!tableNameTypePair.second.empty()) {
9582
5.07k
                if (tableNameTypePair.second == "frame_reference_epoch") {
9583
0
                    sql += "AND ov.frame_reference_epoch IS NOT NULL ";
9584
5.07k
                } else if (tableNameTypePair.second == "ensemble") {
9585
5.03k
                    sql += "AND ov.ensemble_accuracy IS NOT NULL ";
9586
5.03k
                } else {
9587
43
                    sql += "AND ov.type = '";
9588
43
                    sql += tableNameTypePair.second;
9589
43
                    sql += "' ";
9590
43
                }
9591
5.07k
            }
9592
51.1k
            if (deprecated) {
9593
0
                sql += "AND ov.deprecated = 1 ";
9594
0
            }
9595
51.1k
            if (!approximateMatch) {
9596
25.3k
                sql += "AND a.alt_name = ? COLLATE NOCASE ";
9597
25.3k
                params.push_back(searchedNameWithoutDeprecated);
9598
25.3k
            }
9599
51.1k
            if (d->hasAuthorityRestriction()) {
9600
43
                sql += "AND ov.auth_name = ? ";
9601
43
                params.emplace_back(d->authority());
9602
43
            }
9603
51.1k
        }
9604
51.1k
    }
9605
9606
11.2k
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
9607
11.2k
    if (limitResultCount > 0 &&
9608
11.2k
        limitResultCount <
9609
11.2k
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
9610
11.2k
        !approximateMatch) {
9611
2.99k
        sql += " LIMIT ";
9612
2.99k
        sql += toString(static_cast<int>(limitResultCount));
9613
2.99k
    }
9614
9615
11.2k
    std::list<PairObjectName> res;
9616
11.2k
    std::set<std::pair<std::string, std::string>> setIdentified;
9617
9618
    // Querying geodetic datum is a super hot path when importing from WKT1
9619
    // so cache results.
9620
11.2k
    if (allowedObjectTypes.size() == 1 &&
9621
8.76k
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
9622
1.83k
        approximateMatch && d->authority().empty()) {
9623
1.83k
        auto &mapCanonicalizeGRFName =
9624
1.83k
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
9625
1.83k
        if (mapCanonicalizeGRFName.empty()) {
9626
1
            auto sqlRes = d->run(sql, params);
9627
2.49k
            for (const auto &row : sqlRes) {
9628
2.49k
                const auto &name = row[3];
9629
2.49k
                const auto &deprecatedStr = row[4];
9630
2.49k
                const auto canonicalizedName(
9631
2.49k
                    metadata::Identifier::canonicalizeName(name));
9632
2.49k
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
9633
2.49k
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
9634
2.40k
                    v.push_back(row);
9635
2.40k
                }
9636
2.49k
            }
9637
1
        }
9638
1.83k
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
9639
1.83k
        if (iter != mapCanonicalizeGRFName.end()) {
9640
75
            const auto &listOfRow = iter->second;
9641
75
            for (const auto &row : listOfRow) {
9642
75
                const auto &auth_name = row[1];
9643
75
                const auto &code = row[2];
9644
75
                auto key = std::pair<std::string, std::string>(auth_name, code);
9645
75
                if (setIdentified.find(key) != setIdentified.end()) {
9646
0
                    continue;
9647
0
                }
9648
75
                setIdentified.insert(std::move(key));
9649
75
                auto factory = d->createFactory(auth_name);
9650
75
                const auto &name = row[3];
9651
75
                res.emplace_back(
9652
75
                    PairObjectName(factory->createGeodeticDatum(code), name));
9653
75
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9654
75
                    break;
9655
75
                }
9656
75
            }
9657
1.75k
        } else {
9658
2.94M
            for (const auto &pair : mapCanonicalizeGRFName) {
9659
2.94M
                const auto &listOfRow = pair.second;
9660
3.22M
                for (const auto &row : listOfRow) {
9661
3.22M
                    const auto &name = row[3];
9662
3.22M
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9663
3.22M
                                 std::string::npos;
9664
3.22M
                    if (!match) {
9665
3.22M
                        const auto &canonicalizedName(pair.first);
9666
3.22M
                        match = ci_find(canonicalizedName,
9667
3.22M
                                        canonicalizedSearchedName) !=
9668
3.22M
                                std::string::npos;
9669
3.22M
                    }
9670
3.22M
                    if (!match) {
9671
3.22M
                        continue;
9672
3.22M
                    }
9673
9674
580
                    const auto &auth_name = row[1];
9675
580
                    const auto &code = row[2];
9676
580
                    auto key =
9677
580
                        std::pair<std::string, std::string>(auth_name, code);
9678
580
                    if (setIdentified.find(key) != setIdentified.end()) {
9679
0
                        continue;
9680
0
                    }
9681
580
                    setIdentified.insert(std::move(key));
9682
580
                    auto factory = d->createFactory(auth_name);
9683
580
                    res.emplace_back(PairObjectName(
9684
580
                        factory->createGeodeticDatum(code), name));
9685
580
                    if (limitResultCount > 0 &&
9686
580
                        res.size() == limitResultCount) {
9687
580
                        break;
9688
580
                    }
9689
580
                }
9690
2.94M
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9691
580
                    break;
9692
580
                }
9693
2.94M
            }
9694
1.75k
        }
9695
9.42k
    } else {
9696
9.42k
        auto sqlRes = d->run(sql, params);
9697
9.42k
        bool isFirst = true;
9698
9.42k
        bool firstIsDeprecated = false;
9699
9.42k
        size_t countExactMatch = 0;
9700
9.42k
        size_t countExactMatchOnAlias = 0;
9701
9.42k
        std::size_t hashCodeFirstMatch = 0;
9702
48.5M
        for (const auto &row : sqlRes) {
9703
48.5M
            const auto &name = row[3];
9704
48.5M
            if (approximateMatch) {
9705
48.5M
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9706
48.5M
                             std::string::npos;
9707
48.5M
                if (!match) {
9708
48.5M
                    const auto canonicalizedName(
9709
48.5M
                        metadata::Identifier::canonicalizeName(name));
9710
48.5M
                    match =
9711
48.5M
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
9712
48.5M
                        std::string::npos;
9713
48.5M
                }
9714
48.5M
                if (!match) {
9715
48.5M
                    continue;
9716
48.5M
                }
9717
48.5M
            }
9718
6.65k
            const auto &table_name = row[0];
9719
6.65k
            const auto &auth_name = row[1];
9720
6.65k
            const auto &code = row[2];
9721
6.65k
            auto key = std::pair<std::string, std::string>(auth_name, code);
9722
6.65k
            if (setIdentified.find(key) != setIdentified.end()) {
9723
301
                continue;
9724
301
            }
9725
6.35k
            setIdentified.insert(std::move(key));
9726
6.35k
            const auto &deprecatedStr = row[4];
9727
6.35k
            if (isFirst) {
9728
902
                firstIsDeprecated = deprecatedStr == "1";
9729
902
                isFirst = false;
9730
902
            }
9731
6.35k
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
9732
73
                break;
9733
73
            }
9734
6.28k
            auto factory = d->createFactory(auth_name);
9735
6.28k
            auto getObject = [&factory, datumEnsembleAllowed](
9736
6.28k
                                 const std::string &l_table_name,
9737
6.28k
                                 const std::string &l_code)
9738
6.28k
                -> common::IdentifiedObjectNNPtr {
9739
6.28k
                if (l_table_name == "prime_meridian") {
9740
0
                    return factory->createPrimeMeridian(l_code);
9741
6.28k
                } else if (l_table_name == "ellipsoid") {
9742
128
                    return factory->createEllipsoid(l_code);
9743
6.15k
                } else if (l_table_name == "geodetic_datum") {
9744
181
                    if (datumEnsembleAllowed) {
9745
181
                        datum::GeodeticReferenceFramePtr datum;
9746
181
                        datum::DatumEnsemblePtr datumEnsemble;
9747
181
                        constexpr bool turnEnsembleAsDatum = false;
9748
181
                        factory->createGeodeticDatumOrEnsemble(
9749
181
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9750
181
                        if (datum) {
9751
178
                            return NN_NO_CHECK(datum);
9752
178
                        }
9753
181
                        assert(datumEnsemble);
9754
3
                        return NN_NO_CHECK(datumEnsemble);
9755
181
                    }
9756
0
                    return factory->createGeodeticDatum(l_code);
9757
5.97k
                } else if (l_table_name == "vertical_datum") {
9758
65
                    if (datumEnsembleAllowed) {
9759
65
                        datum::VerticalReferenceFramePtr datum;
9760
65
                        datum::DatumEnsemblePtr datumEnsemble;
9761
65
                        constexpr bool turnEnsembleAsDatum = false;
9762
65
                        factory->createVerticalDatumOrEnsemble(
9763
65
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9764
65
                        if (datum) {
9765
62
                            return NN_NO_CHECK(datum);
9766
62
                        }
9767
65
                        assert(datumEnsemble);
9768
3
                        return NN_NO_CHECK(datumEnsemble);
9769
65
                    }
9770
0
                    return factory->createVerticalDatum(l_code);
9771
5.90k
                } else if (l_table_name == "engineering_datum") {
9772
3
                    return factory->createEngineeringDatum(l_code);
9773
5.90k
                } else if (l_table_name == "geodetic_crs") {
9774
1.93k
                    return factory->createGeodeticCRS(l_code);
9775
3.96k
                } else if (l_table_name == "projected_crs") {
9776
2.16k
                    return factory->createProjectedCRS(l_code);
9777
2.16k
                } else if (l_table_name == "derived_projected_crs") {
9778
0
                    return factory->createDerivedProjectedCRS(l_code);
9779
1.80k
                } else if (l_table_name == "vertical_crs") {
9780
471
                    return factory->createVerticalCRS(l_code);
9781
1.33k
                } else if (l_table_name == "compound_crs") {
9782
104
                    return factory->createCompoundCRS(l_code);
9783
1.23k
                } else if (l_table_name == "engineering_crs") {
9784
42
                    return factory->createEngineeringCRS(l_code);
9785
1.19k
                } else if (l_table_name == "conversion") {
9786
237
                    return factory->createConversion(l_code);
9787
953
                } else if (l_table_name == "grid_transformation" ||
9788
652
                           l_table_name == "helmert_transformation" ||
9789
233
                           l_table_name == "other_transformation" ||
9790
953
                           l_table_name == "concatenated_operation") {
9791
953
                    return factory->createCoordinateOperation(l_code, true);
9792
953
                }
9793
0
                throw std::runtime_error("Unsupported table_name");
9794
6.28k
            };
9795
6.28k
            const auto obj = getObject(table_name, code);
9796
6.28k
            if (metadata::Identifier::isEquivalentName(
9797
6.28k
                    obj->nameStr().c_str(), searchedName.c_str(), false)) {
9798
19
                countExactMatch++;
9799
6.26k
            } else if (metadata::Identifier::isEquivalentName(
9800
6.26k
                           name.c_str(), searchedName.c_str(), false)) {
9801
62
                countExactMatchOnAlias++;
9802
62
            }
9803
9804
6.28k
            const auto objPtr = obj.get();
9805
6.28k
            if (res.empty()) {
9806
902
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9807
5.37k
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9808
4.19k
                hashCodeFirstMatch = 0;
9809
4.19k
            }
9810
9811
6.28k
            res.emplace_back(PairObjectName(obj, name));
9812
6.28k
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9813
643
                break;
9814
643
            }
9815
6.28k
        }
9816
9817
        // If we found several objects that are an exact match, and all objects
9818
        // have the same type, and we are not in approximate mode, only keep the
9819
        // objects with the exact name match.
9820
9.42k
        if ((countExactMatch + countExactMatchOnAlias) >= 1 &&
9821
73
            hashCodeFirstMatch != 0 && !approximateMatch) {
9822
11
            std::list<PairObjectName> resTmp;
9823
11
            bool biggerDifferencesAllowed = (countExactMatch == 0);
9824
11
            for (const auto &pair : res) {
9825
11
                if (metadata::Identifier::isEquivalentName(
9826
11
                        pair.first->nameStr().c_str(), searchedName.c_str(),
9827
11
                        biggerDifferencesAllowed) ||
9828
8
                    (countExactMatch == 0 &&
9829
8
                     metadata::Identifier::isEquivalentName(
9830
8
                         pair.second.c_str(), searchedName.c_str(),
9831
11
                         biggerDifferencesAllowed))) {
9832
11
                    resTmp.emplace_back(pair);
9833
11
                }
9834
11
            }
9835
11
            res = std::move(resTmp);
9836
11
        }
9837
9.42k
    }
9838
9839
11.2k
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9840
10.0k
        const auto &aName = a.first->nameStr();
9841
10.0k
        const auto &bName = b.first->nameStr();
9842
9843
10.0k
        if (aName.size() < bName.size()) {
9844
719
            return true;
9845
719
        }
9846
9.32k
        if (aName.size() > bName.size()) {
9847
5.31k
            return false;
9848
5.31k
        }
9849
9850
4.01k
        const auto &aIds = a.first->identifiers();
9851
4.01k
        const auto &bIds = b.first->identifiers();
9852
4.01k
        if (aIds.size() < bIds.size()) {
9853
0
            return true;
9854
0
        }
9855
4.01k
        if (aIds.size() > bIds.size()) {
9856
0
            return false;
9857
0
        }
9858
4.01k
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9859
4.01k
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9860
4.01k
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9861
4.01k
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9862
4.01k
            if (codeSpaceComparison < 0) {
9863
334
                return true;
9864
334
            }
9865
3.67k
            if (codeSpaceComparison > 0) {
9866
447
                return false;
9867
447
            }
9868
3.23k
            const auto &aCode = aIds[idx]->code();
9869
3.23k
            const auto &bCode = bIds[idx]->code();
9870
3.23k
            const auto codeComparison = aCode.compare(bCode);
9871
3.23k
            if (codeComparison < 0) {
9872
970
                return true;
9873
970
            }
9874
2.26k
            if (codeComparison > 0) {
9875
2.26k
                return false;
9876
2.26k
            }
9877
2.26k
        }
9878
0
        return strcmp(typeid(a.first.get()).name(),
9879
0
                      typeid(b.first.get()).name()) < 0;
9880
4.01k
    };
9881
9882
11.2k
    res.sort(sortLambda);
9883
9884
11.2k
    return res;
9885
11.3k
}
9886
//! @endcond
9887
9888
// ---------------------------------------------------------------------------
9889
9890
/** \brief Return a list of area of use from their name
9891
 *
9892
 * @param name Searched name.
9893
 * @param approximateMatch Whether approximate name identification is allowed.
9894
 * @return list of (auth_name, code) of matched objects.
9895
 * @throw FactoryException in case of error.
9896
 */
9897
std::list<std::pair<std::string, std::string>>
9898
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9899
0
                                        bool approximateMatch) const {
9900
0
    std::string sql(
9901
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9902
0
    ListOfParams params;
9903
0
    if (d->hasAuthorityRestriction()) {
9904
0
        sql += " auth_name = ? AND ";
9905
0
        params.emplace_back(d->authority());
9906
0
    }
9907
0
    sql += "name LIKE ?";
9908
0
    if (!approximateMatch) {
9909
0
        params.push_back(name);
9910
0
    } else {
9911
0
        params.push_back('%' + name + '%');
9912
0
    }
9913
0
    auto sqlRes = d->run(sql, params);
9914
0
    std::list<std::pair<std::string, std::string>> res;
9915
0
    for (const auto &row : sqlRes) {
9916
0
        res.emplace_back(row[0], row[1]);
9917
0
    }
9918
0
    return res;
9919
0
}
9920
9921
// ---------------------------------------------------------------------------
9922
9923
//! @cond Doxygen_Suppress
9924
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9925
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9926
0
    std::string sql(
9927
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9928
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9929
0
        "((semi_minor_axis IS NOT NULL AND "
9930
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9931
0
        "((inv_flattening IS NOT NULL AND "
9932
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9933
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9934
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9935
0
                        ellipsoid->computedInverseFlattening()};
9936
0
    auto sqlRes = d->run(sql, params);
9937
0
    std::list<datum::EllipsoidNNPtr> res;
9938
0
    for (const auto &row : sqlRes) {
9939
0
        const auto &auth_name = row[0];
9940
0
        const auto &code = row[1];
9941
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9942
0
    }
9943
0
    return res;
9944
0
}
9945
//! @endcond
9946
9947
// ---------------------------------------------------------------------------
9948
9949
//! @cond Doxygen_Suppress
9950
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9951
    const std::string &datum_auth_name, const std::string &datum_code,
9952
80
    const std::string &geodetic_crs_type) const {
9953
80
    std::string sql(
9954
80
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9955
80
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9956
80
    ListOfParams params{datum_auth_name, datum_code};
9957
80
    if (d->hasAuthorityRestriction()) {
9958
0
        sql += " AND auth_name = ?";
9959
0
        params.emplace_back(d->authority());
9960
0
    }
9961
80
    if (!geodetic_crs_type.empty()) {
9962
0
        sql += " AND type = ?";
9963
0
        params.emplace_back(geodetic_crs_type);
9964
0
    }
9965
80
    sql += " ORDER BY auth_name, code";
9966
80
    auto sqlRes = d->run(sql, params);
9967
80
    std::list<crs::GeodeticCRSNNPtr> res;
9968
325
    for (const auto &row : sqlRes) {
9969
325
        const auto &auth_name = row[0];
9970
325
        const auto &code = row[1];
9971
325
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9972
325
    }
9973
80
    return res;
9974
80
}
9975
//! @endcond
9976
9977
// ---------------------------------------------------------------------------
9978
9979
//! @cond Doxygen_Suppress
9980
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9981
    const datum::GeodeticReferenceFrameNNPtr &datum,
9982
    const std::string &preferredAuthName,
9983
83
    const std::string &geodetic_crs_type) const {
9984
83
    std::list<crs::GeodeticCRSNNPtr> candidates;
9985
83
    const auto &ids = datum->identifiers();
9986
83
    const auto &datumName = datum->nameStr();
9987
83
    if (!ids.empty()) {
9988
80
        for (const auto &id : ids) {
9989
80
            const auto &authName = *(id->codeSpace());
9990
80
            const auto &code = id->code();
9991
80
            if (!authName.empty()) {
9992
80
                const auto tmpFactory =
9993
80
                    (preferredAuthName == authName)
9994
80
                        ? create(databaseContext(), authName)
9995
80
                        : NN_NO_CHECK(d->getSharedFromThis());
9996
80
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9997
80
                    authName, code, geodetic_crs_type);
9998
325
                for (const auto &candidate : l_candidates) {
9999
325
                    candidates.emplace_back(candidate);
10000
325
                }
10001
80
            }
10002
80
        }
10003
80
    } else if (datumName != "unknown" && datumName != "unnamed") {
10004
3
        auto matches = createObjectsFromName(
10005
3
            datumName,
10006
3
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
10007
3
            2);
10008
3
        if (matches.size() == 1) {
10009
0
            const auto &match = matches.front();
10010
0
            if (datum->_isEquivalentTo(match.get(),
10011
0
                                       util::IComparable::Criterion::EQUIVALENT,
10012
0
                                       databaseContext().as_nullable()) &&
10013
0
                !match->identifiers().empty()) {
10014
0
                return createGeodeticCRSFromDatum(
10015
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
10016
0
                        match),
10017
0
                    preferredAuthName, geodetic_crs_type);
10018
0
            }
10019
0
        }
10020
3
    }
10021
83
    return candidates;
10022
83
}
10023
//! @endcond
10024
10025
// ---------------------------------------------------------------------------
10026
10027
//! @cond Doxygen_Suppress
10028
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
10029
0
    const std::string &datum_auth_name, const std::string &datum_code) const {
10030
0
    std::string sql(
10031
0
        "SELECT auth_name, code FROM vertical_crs WHERE "
10032
0
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
10033
0
    ListOfParams params{datum_auth_name, datum_code};
10034
0
    if (d->hasAuthorityRestriction()) {
10035
0
        sql += " AND auth_name = ?";
10036
0
        params.emplace_back(d->authority());
10037
0
    }
10038
0
    auto sqlRes = d->run(sql, params);
10039
0
    std::list<crs::VerticalCRSNNPtr> res;
10040
0
    for (const auto &row : sqlRes) {
10041
0
        const auto &auth_name = row[0];
10042
0
        const auto &code = row[1];
10043
0
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
10044
0
    }
10045
0
    return res;
10046
0
}
10047
//! @endcond
10048
10049
// ---------------------------------------------------------------------------
10050
10051
//! @cond Doxygen_Suppress
10052
std::list<crs::GeodeticCRSNNPtr>
10053
AuthorityFactory::createGeodeticCRSFromEllipsoid(
10054
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
10055
0
    const std::string &geodetic_crs_type) const {
10056
0
    std::string sql(
10057
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
10058
0
        "JOIN geodetic_datum ON "
10059
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
10060
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
10061
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
10062
0
        "geodetic_datum.ellipsoid_code = ? AND "
10063
0
        "geodetic_datum.deprecated = 0 AND "
10064
0
        "geodetic_crs.deprecated = 0");
10065
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
10066
0
    if (d->hasAuthorityRestriction()) {
10067
0
        sql += " AND geodetic_crs.auth_name = ?";
10068
0
        params.emplace_back(d->authority());
10069
0
    }
10070
0
    if (!geodetic_crs_type.empty()) {
10071
0
        sql += " AND geodetic_crs.type = ?";
10072
0
        params.emplace_back(geodetic_crs_type);
10073
0
    }
10074
0
    auto sqlRes = d->run(sql, params);
10075
0
    std::list<crs::GeodeticCRSNNPtr> res;
10076
0
    for (const auto &row : sqlRes) {
10077
0
        const auto &auth_name = row[0];
10078
0
        const auto &code = row[1];
10079
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
10080
0
    }
10081
0
    return res;
10082
0
}
10083
//! @endcond
10084
10085
// ---------------------------------------------------------------------------
10086
10087
//! @cond Doxygen_Suppress
10088
static std::string buildSqlLookForAuthNameCode(
10089
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
10090
0
    const char *prefixField) {
10091
0
    std::string sql("(");
10092
10093
0
    std::set<std::string> authorities;
10094
0
    for (const auto &crs : list) {
10095
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10096
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10097
0
                                   : crs.first->identifiers();
10098
0
        if (!ids.empty()) {
10099
0
            authorities.insert(*(ids[0]->codeSpace()));
10100
0
        }
10101
0
    }
10102
0
    bool firstAuth = true;
10103
0
    for (const auto &auth_name : authorities) {
10104
0
        if (!firstAuth) {
10105
0
            sql += " OR ";
10106
0
        }
10107
0
        firstAuth = false;
10108
0
        sql += "( ";
10109
0
        sql += prefixField;
10110
0
        sql += "auth_name = ? AND ";
10111
0
        sql += prefixField;
10112
0
        sql += "code IN (";
10113
0
        params.emplace_back(auth_name);
10114
0
        bool firstGeodCRSForAuth = true;
10115
0
        for (const auto &crs : list) {
10116
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10117
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10118
0
                                       : crs.first->identifiers();
10119
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
10120
0
                if (!firstGeodCRSForAuth) {
10121
0
                    sql += ',';
10122
0
                }
10123
0
                firstGeodCRSForAuth = false;
10124
0
                sql += '?';
10125
0
                params.emplace_back(ids[0]->code());
10126
0
            }
10127
0
        }
10128
0
        sql += "))";
10129
0
    }
10130
0
    sql += ')';
10131
0
    return sql;
10132
0
}
10133
//! @endcond
10134
10135
// ---------------------------------------------------------------------------
10136
10137
//! @cond Doxygen_Suppress
10138
std::list<crs::ProjectedCRSNNPtr>
10139
AuthorityFactory::createProjectedCRSFromExisting(
10140
0
    const crs::ProjectedCRSNNPtr &crs) const {
10141
0
    std::list<crs::ProjectedCRSNNPtr> res;
10142
10143
0
    const auto &conv = crs->derivingConversionRef();
10144
0
    const auto &method = conv->method();
10145
0
    const auto methodEPSGCode = method->getEPSGCode();
10146
0
    if (methodEPSGCode == 0) {
10147
0
        return res;
10148
0
    }
10149
10150
0
    auto lockedThisFactory(d->getSharedFromThis());
10151
0
    assert(lockedThisFactory);
10152
0
    const auto &baseCRS(crs->baseCRS());
10153
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
10154
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
10155
0
    if (geogCRS) {
10156
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
10157
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
10158
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
10159
0
            const auto &unit =
10160
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
10161
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
10162
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
10163
0
                                        geogCRS->nameStr()),
10164
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
10165
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
10166
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
10167
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
10168
0
            auto otherCandidatesGeodCRS =
10169
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
10170
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
10171
0
                                     otherCandidatesGeodCRS.begin(),
10172
0
                                     otherCandidatesGeodCRS.end());
10173
0
        }
10174
0
    }
10175
10176
0
    std::string sql(
10177
0
        "SELECT projected_crs.auth_name, projected_crs.code, "
10178
0
        "projected_crs.name FROM projected_crs "
10179
0
        "JOIN conversion_table conv ON "
10180
0
        "projected_crs.conversion_auth_name = conv.auth_name AND "
10181
0
        "projected_crs.conversion_code = conv.code "
10182
0
        "JOIN geodetic_crs gcrs ON "
10183
0
        "gcrs.auth_name = projected_crs.geodetic_crs_auth_name AND "
10184
0
        "gcrs.code = projected_crs.geodetic_crs_code "
10185
0
        "JOIN geodetic_datum datum ON "
10186
0
        "datum.auth_name = gcrs.datum_auth_name AND "
10187
0
        "datum.code = gcrs.datum_code "
10188
0
        "JOIN ellipsoid ellps ON "
10189
0
        "ellps.auth_name = datum.ellipsoid_auth_name AND "
10190
0
        "ellps.code = datum.ellipsoid_code "
10191
0
        "WHERE "
10192
0
        "abs(ellps.semi_major_axis - ?) <= 1e-4 * ellps.semi_major_axis AND "
10193
0
        "projected_crs.deprecated = 0 AND ");
10194
0
    ListOfParams params;
10195
0
    params.emplace_back(
10196
0
        toString(crs->baseCRS()->ellipsoid()->semiMajorAxis().value()));
10197
0
    if (!candidatesGeodCRS.empty()) {
10198
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10199
0
                                           "projected_crs.geodetic_crs_");
10200
0
        sql += " AND ";
10201
0
    }
10202
0
    sql += "conv.method_auth_name = 'EPSG' AND "
10203
0
           "conv.method_code = ?";
10204
0
    params.emplace_back(toString(methodEPSGCode));
10205
0
    if (d->hasAuthorityRestriction()) {
10206
0
        sql += " AND projected_crs.auth_name = ?";
10207
0
        params.emplace_back(d->authority());
10208
0
    }
10209
10210
0
    int iParam = 0;
10211
0
    bool hasLat1stStd = false;
10212
0
    double lat1stStd = 0;
10213
0
    int iParamLat1stStd = 0;
10214
0
    bool hasLat2ndStd = false;
10215
0
    double lat2ndStd = 0;
10216
0
    int iParamLat2ndStd = 0;
10217
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
10218
0
        iParam++;
10219
0
        auto opParamvalue =
10220
0
            dynamic_cast<const operation::OperationParameterValue *>(
10221
0
                genOpParamvalue.get());
10222
0
        if (!opParamvalue) {
10223
0
            break;
10224
0
        }
10225
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
10226
0
        const auto &parameterValue = opParamvalue->parameterValue();
10227
0
        if (!(paramEPSGCode > 0 &&
10228
0
              parameterValue->type() ==
10229
0
                  operation::ParameterValue::Type::MEASURE)) {
10230
0
            break;
10231
0
        }
10232
0
        const auto &measure = parameterValue->value();
10233
0
        const auto &unit = measure.unit();
10234
0
        if (unit == common::UnitOfMeasure::DEGREE &&
10235
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
10236
0
            if (methodEPSGCode ==
10237
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
10238
                // Special case for standard parallels of LCC_2SP. See below
10239
0
                if (paramEPSGCode ==
10240
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
10241
0
                    hasLat1stStd = true;
10242
0
                    lat1stStd = measure.value();
10243
0
                    iParamLat1stStd = iParam;
10244
0
                    continue;
10245
0
                } else if (paramEPSGCode ==
10246
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
10247
0
                    hasLat2ndStd = true;
10248
0
                    lat2ndStd = measure.value();
10249
0
                    iParamLat2ndStd = iParam;
10250
0
                    continue;
10251
0
                }
10252
0
            }
10253
0
            const auto iParamAsStr(toString(iParam));
10254
0
            sql += " AND conv.param";
10255
0
            sql += iParamAsStr;
10256
0
            sql += "_code = ? AND conv.param";
10257
0
            sql += iParamAsStr;
10258
0
            sql += "_auth_name = 'EPSG' AND conv.param";
10259
0
            sql += iParamAsStr;
10260
0
            sql += "_value BETWEEN ? AND ?";
10261
            // As angles might be expressed with the odd unit EPSG:9110
10262
            // "sexagesimal DMS", we have to provide a broad range
10263
0
            params.emplace_back(toString(paramEPSGCode));
10264
0
            params.emplace_back(measure.value() - 1);
10265
0
            params.emplace_back(measure.value() + 1);
10266
0
        }
10267
0
    }
10268
10269
    // Special case for standard parallels of LCC_2SP: they can be switched
10270
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
10271
0
        hasLat1stStd && hasLat2ndStd) {
10272
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
10273
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
10274
0
        sql += " AND conv.param";
10275
0
        sql += iParam1AsStr;
10276
0
        sql += "_code = ? AND conv.param";
10277
0
        sql += iParam1AsStr;
10278
0
        sql += "_auth_name = 'EPSG' AND conv.param";
10279
0
        sql += iParam2AsStr;
10280
0
        sql += "_code = ? AND conv.param";
10281
0
        sql += iParam2AsStr;
10282
0
        sql += "_auth_name = 'EPSG' AND ((";
10283
0
        params.emplace_back(
10284
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
10285
0
        params.emplace_back(
10286
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
10287
0
        double val1 = lat1stStd;
10288
0
        double val2 = lat2ndStd;
10289
0
        for (int i = 0; i < 2; i++) {
10290
0
            if (i == 1) {
10291
0
                sql += ") OR (";
10292
0
                std::swap(val1, val2);
10293
0
            }
10294
0
            sql += "conv.param";
10295
0
            sql += iParam1AsStr;
10296
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
10297
0
            sql += iParam2AsStr;
10298
0
            sql += "_value BETWEEN ? AND ?";
10299
0
            params.emplace_back(val1 - 1);
10300
0
            params.emplace_back(val1 + 1);
10301
0
            params.emplace_back(val2 - 1);
10302
0
            params.emplace_back(val2 + 1);
10303
0
        }
10304
0
        sql += "))";
10305
0
    }
10306
0
    auto sqlRes = d->run(sql, params);
10307
10308
0
    for (const auto &row : sqlRes) {
10309
0
        const auto &name = row[2];
10310
0
        if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(),
10311
0
                                                   name.c_str())) {
10312
0
            const auto &auth_name = row[0];
10313
0
            const auto &code = row[1];
10314
0
            res.emplace_back(
10315
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10316
0
        }
10317
0
    }
10318
0
    if (!res.empty()) {
10319
0
        return res;
10320
0
    }
10321
10322
0
    params.clear();
10323
10324
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
10325
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
10326
0
    if (!candidatesGeodCRS.empty()) {
10327
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10328
0
                                           "geodetic_crs_");
10329
0
        sql += " AND ";
10330
0
    }
10331
10332
0
    const auto escapeLikeStr = [](const std::string &str) {
10333
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
10334
0
                          "%", "\\%");
10335
0
    };
10336
10337
0
    const auto ellpsSemiMajorStr =
10338
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
10339
10340
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
10341
10342
    // WKT2 definition
10343
0
    {
10344
0
        std::string patternVal("%");
10345
10346
0
        patternVal += ',';
10347
0
        patternVal += ellpsSemiMajorStr;
10348
0
        patternVal += '%';
10349
10350
0
        patternVal += escapeLikeStr(method->nameStr());
10351
0
        patternVal += '%';
10352
10353
0
        params.emplace_back(patternVal);
10354
0
    }
10355
10356
0
    const auto *mapping = getMapping(method.get());
10357
0
    if (mapping && mapping->proj_name_main) {
10358
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
10359
10360
0
        std::string patternVal("%");
10361
0
        patternVal += "proj=";
10362
0
        patternVal += mapping->proj_name_main;
10363
0
        patternVal += '%';
10364
0
        params.emplace_back(patternVal);
10365
10366
        // could be a= or R=
10367
0
        patternVal = "%=";
10368
0
        patternVal += ellpsSemiMajorStr;
10369
0
        patternVal += '%';
10370
0
        params.emplace_back(patternVal);
10371
10372
0
        std::string projEllpsName;
10373
0
        std::string ellpsName;
10374
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
10375
0
                                                            ellpsName)) {
10376
0
            sql += " OR text_definition LIKE ?";
10377
            // Could be ellps= or datum=
10378
0
            patternVal = "%=";
10379
0
            patternVal += projEllpsName;
10380
0
            patternVal += '%';
10381
0
            params.emplace_back(patternVal);
10382
0
        }
10383
10384
0
        sql += "))";
10385
0
    }
10386
10387
    // WKT1_GDAL definition
10388
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
10389
0
    if (wkt1GDALMethodName) {
10390
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10391
0
        std::string patternVal("%");
10392
10393
0
        patternVal += ',';
10394
0
        patternVal += ellpsSemiMajorStr;
10395
0
        patternVal += '%';
10396
10397
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
10398
0
        patternVal += '%';
10399
10400
0
        params.emplace_back(patternVal);
10401
0
    }
10402
10403
    // WKT1_ESRI definition
10404
0
    const char *esriMethodName = conv->getESRIMethodName();
10405
0
    if (esriMethodName) {
10406
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10407
0
        std::string patternVal("%");
10408
10409
0
        patternVal += ',';
10410
0
        patternVal += ellpsSemiMajorStr;
10411
0
        patternVal += '%';
10412
10413
0
        patternVal += escapeLikeStr(esriMethodName);
10414
0
        patternVal += '%';
10415
10416
0
        auto fe =
10417
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
10418
0
        if (*fe == Measure()) {
10419
0
            fe = &conv->parameterValueMeasure(
10420
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
10421
0
        }
10422
0
        if (!(*fe == Measure())) {
10423
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
10424
0
            patternVal +=
10425
0
                toString(fe->convertToUnit(
10426
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
10427
0
                         10);
10428
0
            patternVal += '%';
10429
0
        }
10430
10431
0
        auto lat = &conv->parameterValueMeasure(
10432
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
10433
0
        if (*lat == Measure()) {
10434
0
            lat = &conv->parameterValueMeasure(
10435
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
10436
0
        }
10437
0
        if (!(*lat == Measure())) {
10438
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
10439
0
            const auto &angularUnit =
10440
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
10441
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
10442
0
                    : UnitOfMeasure::DEGREE;
10443
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
10444
0
            patternVal += '%';
10445
0
        }
10446
10447
0
        params.emplace_back(patternVal);
10448
0
    }
10449
0
    sql += ")";
10450
0
    if (d->hasAuthorityRestriction()) {
10451
0
        sql += " AND auth_name = ?";
10452
0
        params.emplace_back(d->authority());
10453
0
    }
10454
10455
0
    auto sqlRes2 = d->run(sql, params);
10456
10457
0
    if (sqlRes.size() <= 200) {
10458
0
        for (const auto &row : sqlRes) {
10459
0
            const auto &auth_name = row[0];
10460
0
            const auto &code = row[1];
10461
0
            res.emplace_back(
10462
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10463
0
        }
10464
0
    }
10465
0
    if (sqlRes2.size() <= 200) {
10466
0
        for (const auto &row : sqlRes2) {
10467
0
            const auto &auth_name = row[0];
10468
0
            const auto &code = row[1];
10469
0
            res.emplace_back(
10470
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10471
0
        }
10472
0
    }
10473
10474
0
    return res;
10475
0
}
10476
10477
// ---------------------------------------------------------------------------
10478
10479
std::list<crs::CompoundCRSNNPtr>
10480
AuthorityFactory::createCompoundCRSFromExisting(
10481
0
    const crs::CompoundCRSNNPtr &crs) const {
10482
0
    std::list<crs::CompoundCRSNNPtr> res;
10483
10484
0
    auto lockedThisFactory(d->getSharedFromThis());
10485
0
    assert(lockedThisFactory);
10486
10487
0
    const auto &components = crs->componentReferenceSystems();
10488
0
    if (components.size() != 2) {
10489
0
        return res;
10490
0
    }
10491
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
10492
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
10493
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
10494
0
        return res;
10495
0
    }
10496
10497
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
10498
0
                    "deprecated = 0 AND ");
10499
0
    ListOfParams params;
10500
0
    bool addAnd = false;
10501
0
    if (!candidatesHorizCRS.empty()) {
10502
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
10503
0
                                           "horiz_crs_");
10504
0
        addAnd = true;
10505
0
    }
10506
0
    if (!candidatesVertCRS.empty()) {
10507
0
        if (addAnd) {
10508
0
            sql += " AND ";
10509
0
        }
10510
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
10511
0
                                           "vertical_crs_");
10512
0
        addAnd = true;
10513
0
    }
10514
0
    if (d->hasAuthorityRestriction()) {
10515
0
        if (addAnd) {
10516
0
            sql += " AND ";
10517
0
        }
10518
0
        sql += "auth_name = ?";
10519
0
        params.emplace_back(d->authority());
10520
0
    }
10521
10522
0
    auto sqlRes = d->run(sql, params);
10523
0
    for (const auto &row : sqlRes) {
10524
0
        const auto &auth_name = row[0];
10525
0
        const auto &code = row[1];
10526
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
10527
0
    }
10528
0
    return res;
10529
0
}
10530
10531
// ---------------------------------------------------------------------------
10532
10533
std::vector<operation::CoordinateOperationNNPtr>
10534
AuthorityFactory::getTransformationsForGeoid(
10535
0
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
10536
0
    std::vector<operation::CoordinateOperationNNPtr> res;
10537
10538
0
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
10539
0
                          "geoid_model WHERE name = ?");
10540
0
    auto sqlRes = d->run(sql, {geoidName});
10541
0
    for (const auto &row : sqlRes) {
10542
0
        const auto &auth_name = row[0];
10543
0
        const auto &code = row[1];
10544
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
10545
0
            code, usePROJAlternativeGridNames));
10546
0
    }
10547
10548
0
    return res;
10549
0
}
10550
10551
// ---------------------------------------------------------------------------
10552
10553
std::vector<operation::PointMotionOperationNNPtr>
10554
AuthorityFactory::getPointMotionOperationsFor(
10555
83
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
10556
83
    std::vector<operation::PointMotionOperationNNPtr> res;
10557
83
    const auto crsList =
10558
83
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
10559
83
                                   /* preferredAuthName = */ std::string(),
10560
83
                                   /* geodetic_crs_type = */ std::string());
10561
83
    if (crsList.empty())
10562
14
        return res;
10563
69
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
10564
69
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
10565
69
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
10566
69
                    "(");
10567
69
    bool addOr = false;
10568
69
    ListOfParams params;
10569
325
    for (const auto &candidateCrs : crsList) {
10570
325
        if (addOr)
10571
256
            sql += " OR ";
10572
325
        addOr = true;
10573
325
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
10574
325
        const auto &ids = candidateCrs->identifiers();
10575
325
        params.emplace_back(*(ids[0]->codeSpace()));
10576
325
        params.emplace_back(ids[0]->code());
10577
325
    }
10578
69
    sql += ")";
10579
69
    if (d->hasAuthorityRestriction()) {
10580
0
        sql += " AND auth_name = ?";
10581
0
        params.emplace_back(d->authority());
10582
0
    }
10583
10584
69
    auto sqlRes = d->run(sql, params);
10585
69
    for (const auto &row : sqlRes) {
10586
0
        const auto &auth_name = row[0];
10587
0
        const auto &code = row[1];
10588
0
        auto pmo =
10589
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
10590
0
                d->createFactory(auth_name)->createCoordinateOperation(
10591
0
                    code, usePROJAlternativeGridNames));
10592
0
        if (pmo) {
10593
0
            res.emplace_back(NN_NO_CHECK(pmo));
10594
0
        }
10595
0
    }
10596
69
    return res;
10597
83
}
10598
10599
//! @endcond
10600
10601
// ---------------------------------------------------------------------------
10602
10603
//! @cond Doxygen_Suppress
10604
6.93k
FactoryException::FactoryException(const char *message) : Exception(message) {}
10605
10606
// ---------------------------------------------------------------------------
10607
10608
FactoryException::FactoryException(const std::string &message)
10609
1.65k
    : Exception(message) {}
10610
10611
// ---------------------------------------------------------------------------
10612
10613
8.58k
FactoryException::~FactoryException() = default;
10614
10615
// ---------------------------------------------------------------------------
10616
10617
0
FactoryException::FactoryException(const FactoryException &) = default;
10618
//! @endcond
10619
10620
// ---------------------------------------------------------------------------
10621
10622
//! @cond Doxygen_Suppress
10623
10624
struct NoSuchAuthorityCodeException::Private {
10625
    std::string authority_;
10626
    std::string code_;
10627
10628
    Private(const std::string &authority, const std::string &code)
10629
1.64k
        : authority_(authority), code_(code) {}
10630
};
10631
10632
// ---------------------------------------------------------------------------
10633
10634
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10635
    const std::string &message, const std::string &authority,
10636
    const std::string &code)
10637
1.64k
    : FactoryException(message), d(std::make_unique<Private>(authority, code)) {
10638
1.64k
}
10639
10640
// ---------------------------------------------------------------------------
10641
10642
1.64k
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
10643
10644
// ---------------------------------------------------------------------------
10645
10646
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10647
    const NoSuchAuthorityCodeException &other)
10648
0
    : FactoryException(other), d(std::make_unique<Private>(*(other.d))) {}
10649
//! @endcond
10650
10651
// ---------------------------------------------------------------------------
10652
10653
/** \brief Returns authority name. */
10654
296
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
10655
296
    return d->authority_;
10656
296
}
10657
10658
// ---------------------------------------------------------------------------
10659
10660
/** \brief Returns authority code. */
10661
296
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
10662
296
    return d->code_;
10663
296
}
10664
10665
// ---------------------------------------------------------------------------
10666
10667
} // namespace io
10668
NS_PROJ_END
10669
10670
// ---------------------------------------------------------------------------
10671
10672
0
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }