Coverage Report

Created: 2026-06-09 07:13

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
0
#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
110
0
#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
111
#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
112
113
// Coordinate system types
114
constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE;
115
constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE;
116
constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE;
117
constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE;
118
constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE;
119
120
// See data/sql/metadata.sql for the semantics of those constants
121
constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
122
// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
123
// must be incremented.
124
constexpr int DATABASE_LAYOUT_VERSION_MINOR = 7;
125
126
constexpr size_t N_MAX_PARAMS = 7;
127
128
#ifdef EMBED_RESOURCE_FILES
129
constexpr const char *EMBEDDED_PROJ_DB = "__embedded_proj_db__";
130
#endif
131
132
// ---------------------------------------------------------------------------
133
134
struct SQLValues {
135
    enum class Type { STRING, INT, DOUBLE };
136
137
    // cppcheck-suppress noExplicitConstructor
138
3.01M
    SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
139
140
    // cppcheck-suppress noExplicitConstructor
141
0
    SQLValues(int value) : type_(Type::INT), int_(value) {}
142
143
    // cppcheck-suppress noExplicitConstructor
144
51.0k
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
145
146
3.43M
    const Type &type() const { return type_; }
147
148
    // cppcheck-suppress functionStatic
149
3.23M
    const std::string &stringValue() const { return str_; }
150
151
    // cppcheck-suppress functionStatic
152
0
    int intValue() const { return int_; }
153
154
    // cppcheck-suppress functionStatic
155
194k
    double doubleValue() const { return double_; }
156
157
  private:
158
    Type type_;
159
    std::string str_{};
160
    int int_ = 0;
161
    double double_ = 0.0;
162
};
163
164
// ---------------------------------------------------------------------------
165
166
using SQLRow = std::vector<std::string>;
167
using SQLResultSet = std::list<SQLRow>;
168
using ListOfParams = std::list<SQLValues>;
169
170
// ---------------------------------------------------------------------------
171
172
1.97M
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
173
1.97M
    switch (sqlite3_value_type(val)) {
174
1.97M
    case SQLITE_FLOAT:
175
1.97M
        gotVal = true;
176
1.97M
        return sqlite3_value_double(val);
177
178
0
    case SQLITE_INTEGER:
179
0
        gotVal = true;
180
0
        return static_cast<double>(sqlite3_value_int64(val));
181
182
0
    default:
183
0
        gotVal = false;
184
0
        return 0.0;
185
1.97M
    }
186
1.97M
}
187
188
// ---------------------------------------------------------------------------
189
190
static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
191
                                              int /* argc */,
192
53.0k
                                              sqlite3_value **argv) {
193
53.0k
    bool b0, b1, b2, b3;
194
53.0k
    double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
195
53.0k
    double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
196
53.0k
    double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
197
53.0k
    double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
198
53.0k
    if (!b0 || !b1 || !b2 || !b3) {
199
0
        sqlite3_result_null(pContext);
200
0
        return;
201
0
    }
202
    // Deal with area crossing antimeridian
203
53.0k
    if (east_lon < west_lon) {
204
758
        east_lon += 360.0;
205
758
    }
206
    // Integrate cos(lat) between south_lat and north_lat
207
53.0k
    double pseudo_area = (east_lon - west_lon) *
208
53.0k
                         (std::sin(common::Angle(north_lat).getSIValue()) -
209
53.0k
                          std::sin(common::Angle(south_lat).getSIValue()));
210
53.0k
    sqlite3_result_double(pContext, pseudo_area);
211
53.0k
}
212
213
// ---------------------------------------------------------------------------
214
215
static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
216
219k
                                        int /* argc */, sqlite3_value **argv) {
217
219k
    bool b0, b1, b2, b3, b4, b5, b6, b7;
218
219k
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
219
219k
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
220
219k
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
221
219k
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
222
219k
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
223
219k
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
224
219k
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
225
219k
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
226
219k
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
227
0
        sqlite3_result_null(pContext);
228
0
        return;
229
0
    }
230
219k
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
231
219k
                                                         east_lon1, north_lat1);
232
219k
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
233
219k
                                                         east_lon2, north_lat2);
234
219k
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
235
219k
}
236
237
// ---------------------------------------------------------------------------
238
239
class SQLiteHandle {
240
    std::string path_{};
241
    sqlite3 *sqlite_handle_ = nullptr;
242
    bool close_handle_ = true;
243
244
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
245
    bool is_valid_ = true;
246
#endif
247
248
    int nLayoutVersionMajor_ = 0;
249
    int nLayoutVersionMinor_ = 0;
250
251
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
252
    std::unique_ptr<SQLite3VFS> vfs_{};
253
#endif
254
255
    SQLiteHandle(const SQLiteHandle &) = delete;
256
    SQLiteHandle &operator=(const SQLiteHandle &) = delete;
257
258
    SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
259
9.83k
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
260
9.83k
        assert(sqlite_handle_);
261
9.83k
    }
262
263
    // cppcheck-suppress functionStatic
264
    void initialize();
265
266
    SQLResultSet run(const std::string &sql,
267
                     const ListOfParams &parameters = ListOfParams(),
268
                     bool useMaxFloatPrecision = false);
269
270
  public:
271
    ~SQLiteHandle();
272
273
9.83k
    const std::string &path() const { return path_; }
274
275
81.8k
    sqlite3 *handle() { return sqlite_handle_; }
276
277
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
278
1.15M
    bool isValid() const { return is_valid_; }
279
280
0
    void invalidate() { is_valid_ = false; }
281
#endif
282
283
    static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
284
                                              const std::string &path);
285
286
    // might not be shared between thread depending how the handle was opened!
287
    static std::shared_ptr<SQLiteHandle>
288
    initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
289
                     int nLayoutVersionMajor, int nLayoutVersionMinor);
290
291
    static std::unique_ptr<SQLiteHandle>
292
    initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
293
294
    void checkDatabaseLayout(const std::string &mainDbPath,
295
                             const std::string &path,
296
                             const std::string &dbNamePrefix);
297
298
    SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
299
                     const ListOfParams &parameters = ListOfParams(),
300
                     bool useMaxFloatPrecision = false);
301
302
0
    inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
303
0
    inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
304
};
305
306
// ---------------------------------------------------------------------------
307
308
9.83k
SQLiteHandle::~SQLiteHandle() {
309
9.83k
    if (close_handle_) {
310
9.83k
        sqlite3_close(sqlite_handle_);
311
9.83k
    }
312
9.83k
}
313
314
// ---------------------------------------------------------------------------
315
316
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
317
9.83k
                                                 const std::string &pathIn) {
318
319
9.83k
    std::string path(pathIn);
320
9.83k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
321
    // Minimum version for correct performance: 3.11
322
9.83k
    if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
323
0
        pj_log(ctx, PJ_LOG_ERROR,
324
0
               "SQLite3 version is %s, whereas at least 3.11 should be used",
325
0
               sqlite3_libversion());
326
0
    }
327
328
9.83k
    std::string vfsName;
329
9.83k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
330
9.83k
    std::unique_ptr<SQLite3VFS> vfs;
331
9.83k
#endif
332
333
9.83k
#ifdef EMBED_RESOURCE_FILES
334
9.83k
    if (path == EMBEDDED_PROJ_DB && ctx->custom_sqlite3_vfs_name.empty()) {
335
0
        unsigned int proj_db_size = 0;
336
0
        const unsigned char *proj_db = pj_get_embedded_proj_db(&proj_db_size);
337
338
0
        vfs = SQLite3VFS::createMem(proj_db, proj_db_size);
339
0
        if (vfs == nullptr) {
340
0
            throw FactoryException("Open of " + path + " failed");
341
0
        }
342
343
0
        std::ostringstream buffer;
344
0
        buffer << "file:/proj.db?immutable=1&ptr=";
345
0
        buffer << reinterpret_cast<uintptr_t>(proj_db);
346
0
        buffer << "&sz=";
347
0
        buffer << proj_db_size;
348
0
        buffer << "&max=";
349
0
        buffer << proj_db_size;
350
0
        buffer << "&vfs=";
351
0
        buffer << vfs->name();
352
0
        path = buffer.str();
353
0
    } else
354
9.83k
#endif
355
356
9.83k
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
357
9.83k
        if (ctx->custom_sqlite3_vfs_name.empty()) {
358
9.83k
        vfs = SQLite3VFS::create(false, true, true);
359
9.83k
        if (vfs == nullptr) {
360
0
            throw FactoryException("Open of " + path + " failed");
361
0
        }
362
9.83k
        vfsName = vfs->name();
363
9.83k
    } else
364
0
#endif
365
0
    {
366
0
        vfsName = ctx->custom_sqlite3_vfs_name;
367
0
    }
368
9.83k
    sqlite3 *sqlite_handle = nullptr;
369
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
370
9.83k
    if (sqlite3_open_v2(
371
9.83k
            path.c_str(), &sqlite_handle,
372
9.83k
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
373
9.83k
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
374
9.83k
        !sqlite_handle) {
375
0
        if (sqlite_handle != nullptr) {
376
0
            sqlite3_close(sqlite_handle);
377
0
        }
378
0
        throw FactoryException("Open of " + path + " failed");
379
0
    }
380
9.83k
    auto handle =
381
9.83k
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
382
9.83k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
383
9.83k
    handle->vfs_ = std::move(vfs);
384
9.83k
#endif
385
9.83k
    handle->initialize();
386
9.83k
    handle->path_ = path;
387
9.83k
    handle->checkDatabaseLayout(path, path, std::string());
388
9.83k
    return handle;
389
9.83k
}
390
391
// ---------------------------------------------------------------------------
392
393
std::shared_ptr<SQLiteHandle>
394
SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
395
                               int nLayoutVersionMajor,
396
0
                               int nLayoutVersionMinor) {
397
0
    auto handle = std::shared_ptr<SQLiteHandle>(
398
0
        new SQLiteHandle(sqlite_handle, close_handle));
399
0
    handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
400
0
    handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
401
0
    handle->initialize();
402
0
    return handle;
403
0
}
404
405
// ---------------------------------------------------------------------------
406
407
std::unique_ptr<SQLiteHandle>
408
SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
409
0
                                        bool close_handle) {
410
0
    auto handle = std::unique_ptr<SQLiteHandle>(
411
0
        new SQLiteHandle(sqlite_handle, close_handle));
412
0
    handle->initialize();
413
0
    return handle;
414
0
}
415
416
// ---------------------------------------------------------------------------
417
418
SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
419
                               const ListOfParams &parameters,
420
1.16M
                               bool useMaxFloatPrecision) {
421
1.16M
    int nBindField = 1;
422
3.43M
    for (const auto &param : parameters) {
423
3.43M
        const auto &paramType = param.type();
424
3.43M
        if (paramType == SQLValues::Type::STRING) {
425
3.23M
            const auto &strValue = param.stringValue();
426
3.23M
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
427
3.23M
                              static_cast<int>(strValue.size()),
428
3.23M
                              SQLITE_TRANSIENT);
429
3.23M
        } else if (paramType == SQLValues::Type::INT) {
430
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
431
194k
        } else {
432
194k
            assert(paramType == SQLValues::Type::DOUBLE);
433
194k
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
434
194k
        }
435
3.43M
        nBindField++;
436
3.43M
    }
437
438
#ifdef TRACE_DATABASE
439
    size_t nPos = 0;
440
    std::string sqlSubst(sql);
441
    for (const auto &param : parameters) {
442
        nPos = sqlSubst.find('?', nPos);
443
        assert(nPos != std::string::npos);
444
        std::string strValue;
445
        const auto paramType = param.type();
446
        if (paramType == SQLValues::Type::STRING) {
447
            strValue = '\'' + param.stringValue() + '\'';
448
        } else if (paramType == SQLValues::Type::INT) {
449
            strValue = toString(param.intValue());
450
        } else {
451
            strValue = toString(param.doubleValue());
452
        }
453
        sqlSubst =
454
            sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
455
        nPos += strValue.size();
456
    }
457
    logTrace(sqlSubst, "DATABASE");
458
#endif
459
460
1.16M
    SQLResultSet result;
461
1.16M
    const int column_count = sqlite3_column_count(stmt);
462
66.3M
    while (true) {
463
66.3M
        int ret = sqlite3_step(stmt);
464
66.3M
        if (ret == SQLITE_ROW) {
465
65.2M
            SQLRow row(column_count);
466
461M
            for (int i = 0; i < column_count; i++) {
467
396M
                if (useMaxFloatPrecision &&
468
62.3k
                    sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
469
                    // sqlite3_column_text() does not use maximum precision
470
15.5k
                    std::ostringstream buffer;
471
15.5k
                    buffer.imbue(std::locale::classic());
472
15.5k
                    buffer << std::setprecision(18);
473
15.5k
                    buffer << sqlite3_column_double(stmt, i);
474
15.5k
                    row[i] = buffer.str();
475
396M
                } else {
476
396M
                    const char *txt = reinterpret_cast<const char *>(
477
396M
                        sqlite3_column_text(stmt, i));
478
396M
                    if (txt) {
479
393M
                        row[i] = txt;
480
393M
                    }
481
396M
                }
482
396M
            }
483
65.2M
            result.emplace_back(std::move(row));
484
65.2M
        } else if (ret == SQLITE_DONE) {
485
1.16M
            break;
486
1.16M
        } else {
487
0
            throw FactoryException(std::string("SQLite error [ ")
488
0
                                       .append("code = ")
489
0
                                       .append(internal::toString(ret))
490
0
                                       .append(", msg = ")
491
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
492
0
                                       .append(" ] on ")
493
0
                                       .append(sql));
494
0
        }
495
66.3M
    }
496
1.16M
    return result;
497
1.16M
}
498
499
// ---------------------------------------------------------------------------
500
501
SQLResultSet SQLiteHandle::run(const std::string &sql,
502
                               const ListOfParams &parameters,
503
9.83k
                               bool useMaxFloatPrecision) {
504
9.83k
    sqlite3_stmt *stmt = nullptr;
505
9.83k
    try {
506
9.83k
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
507
9.83k
                               static_cast<int>(sql.size()), &stmt,
508
9.83k
                               nullptr) != SQLITE_OK) {
509
0
            throw FactoryException(std::string("SQLite error [ ")
510
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
511
0
                                       .append(" ] on ")
512
0
                                       .append(sql));
513
0
        }
514
9.83k
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
515
9.83k
        sqlite3_finalize(stmt);
516
9.83k
        return ret;
517
9.83k
    } catch (const std::exception &) {
518
0
        if (stmt)
519
0
            sqlite3_finalize(stmt);
520
0
        throw;
521
0
    }
522
9.83k
}
523
524
// ---------------------------------------------------------------------------
525
526
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
527
                                       const std::string &path,
528
9.83k
                                       const std::string &dbNamePrefix) {
529
9.83k
    if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
530
0
                                     "sqlite_master WHERE name = 'metadata'")
531
0
                                     .empty()) {
532
        // Accept auxiliary databases without metadata table (sparse DBs)
533
0
        return;
534
0
    }
535
9.83k
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
536
9.83k
                   "metadata WHERE key IN "
537
9.83k
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
538
9.83k
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
539
9.83k
    if (res.empty() && !dbNamePrefix.empty()) {
540
        // Accept auxiliary databases without layout metadata.
541
0
        return;
542
0
    }
543
9.83k
    if (res.size() != 2) {
544
0
        throw FactoryException(
545
0
            path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
546
0
                   "DATABASE.LAYOUT.VERSION.MINOR "
547
0
                   "metadata. It comes from another PROJ installation.");
548
0
    }
549
9.83k
    int major = 0;
550
9.83k
    int minor = 0;
551
19.6k
    for (const auto &row : res) {
552
19.6k
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
553
9.83k
            major = atoi(row[1].c_str());
554
9.83k
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
555
9.83k
            minor = atoi(row[1].c_str());
556
9.83k
        }
557
19.6k
    }
558
9.83k
    if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
559
0
        throw FactoryException(
560
0
            path +
561
0
            " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
562
0
            " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
563
0
            " is expected. "
564
0
            "It comes from another PROJ installation.");
565
0
    }
566
567
9.83k
    if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
568
0
        throw FactoryException(
569
0
            path +
570
0
            " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
571
0
            " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
572
0
            " is expected. "
573
0
            "It comes from another PROJ installation.");
574
0
    }
575
576
9.83k
    if (dbNamePrefix.empty()) {
577
9.83k
        nLayoutVersionMajor_ = major;
578
9.83k
        nLayoutVersionMinor_ = minor;
579
9.83k
    } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
580
0
        throw FactoryException(
581
0
            "Auxiliary database " + path +
582
0
            " contains a DATABASE.LAYOUT.VERSION =  " + toString(major) + '.' +
583
0
            toString(minor) +
584
0
            " which is different from the one from the main database " +
585
0
            mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
586
0
            toString(nLayoutVersionMinor_));
587
0
    }
588
9.83k
}
589
590
// ---------------------------------------------------------------------------
591
592
#ifndef SQLITE_DETERMINISTIC
593
#define SQLITE_DETERMINISTIC 0
594
#endif
595
596
9.83k
void SQLiteHandle::initialize() {
597
598
    // There is a bug in sqlite 3.38.0 with some complex queries.
599
    // Cf https://github.com/OSGeo/PROJ/issues/3077
600
    // Disabling Bloom-filter pull-down optimization as suggested in
601
    // https://sqlite.org/forum/forumpost/7d3a75438c
602
9.83k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
603
9.83k
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
604
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
605
0
                             0x100000);
606
0
    }
607
608
9.83k
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
609
9.83k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
610
9.83k
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
611
9.83k
                            nullptr);
612
613
9.83k
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
614
9.83k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
615
9.83k
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
616
9.83k
}
617
618
// ---------------------------------------------------------------------------
619
620
class SQLiteHandleCache {
621
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
622
    bool firstTime_ = true;
623
#endif
624
625
    std::mutex sMutex_{};
626
627
    // Map dbname to SQLiteHandle
628
    lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
629
630
  public:
631
    static SQLiteHandleCache &get();
632
633
    std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
634
                                            PJ_CONTEXT *ctx);
635
636
    void clear();
637
638
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
639
    void invalidateHandles();
640
#endif
641
};
642
643
// ---------------------------------------------------------------------------
644
645
21.2k
SQLiteHandleCache &SQLiteHandleCache::get() {
646
    // Global cache
647
21.2k
    static SQLiteHandleCache gSQLiteHandleCache;
648
21.2k
    return gSQLiteHandleCache;
649
21.2k
}
650
651
// ---------------------------------------------------------------------------
652
653
11.4k
void SQLiteHandleCache::clear() {
654
11.4k
    std::lock_guard<std::mutex> lock(sMutex_);
655
11.4k
    cache_.clear();
656
11.4k
}
657
658
// ---------------------------------------------------------------------------
659
660
std::shared_ptr<SQLiteHandle>
661
9.83k
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
662
9.83k
    std::lock_guard<std::mutex> lock(sMutex_);
663
664
9.83k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
665
9.83k
    if (firstTime_) {
666
1
        firstTime_ = false;
667
1
        pthread_atfork(
668
1
            []() {
669
                // This mutex needs to be acquired by 'invalidateHandles()'.
670
                // The forking thread needs to own this mutex during the fork.
671
                // Otherwise there's an opporunity for another thread to own
672
                // the mutex during the fork, leaving the child process unable
673
                // to acquire the mutex in invalidateHandles().
674
0
                SQLiteHandleCache::get().sMutex_.lock();
675
0
            },
676
1
            []() { SQLiteHandleCache::get().sMutex_.unlock(); },
677
1
            []() {
678
0
                SQLiteHandleCache::get().sMutex_.unlock();
679
0
                SQLiteHandleCache::get().invalidateHandles();
680
0
            });
681
1
    }
682
9.83k
#endif
683
684
9.83k
    std::shared_ptr<SQLiteHandle> handle;
685
9.83k
    std::string key = path + ctx->custom_sqlite3_vfs_name;
686
9.83k
    if (!cache_.tryGet(key, handle)) {
687
9.83k
        handle = SQLiteHandle::open(ctx, path);
688
9.83k
        cache_.insert(key, handle);
689
9.83k
    }
690
9.83k
    return handle;
691
9.83k
}
692
693
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
694
// ---------------------------------------------------------------------------
695
696
0
void SQLiteHandleCache::invalidateHandles() {
697
0
    std::lock_guard<std::mutex> lock(sMutex_);
698
0
    const auto lambda =
699
0
        [](const lru11::KeyValuePair<std::string, std::shared_ptr<SQLiteHandle>>
700
0
               &kvp) { kvp.value->invalidate(); };
701
0
    cache_.cwalk(lambda);
702
0
    cache_.clear();
703
0
}
704
#endif
705
706
// ---------------------------------------------------------------------------
707
708
struct DatabaseContext::Private {
709
    Private();
710
    ~Private();
711
712
    void open(const std::string &databasePath, PJ_CONTEXT *ctx);
713
    void setHandle(sqlite3 *sqlite_handle);
714
715
    const std::shared_ptr<SQLiteHandle> &handle();
716
717
241k
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
718
9.83k
    void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
719
720
    SQLResultSet run(const std::string &sql,
721
                     const ListOfParams &parameters = ListOfParams(),
722
                     bool useMaxFloatPrecision = false);
723
724
    std::vector<std::string> getDatabaseStructure();
725
726
    // cppcheck-suppress functionStatic
727
0
    const std::string &getPath() const { return databasePath_; }
728
729
    void attachExtraDatabases(
730
        const std::vector<std::string> &auxiliaryDatabasePaths);
731
732
    // Mechanism to detect recursion in calls from
733
    // AuthorityFactory::createXXX() -> createFromUserInput() ->
734
    // AuthorityFactory::createXXX()
735
    struct RecursionDetector {
736
        explicit RecursionDetector(const DatabaseContextNNPtr &context)
737
307
            : dbContext_(context) {
738
307
            if (dbContext_->getPrivate()->recLevel_ == 2) {
739
                // Throw exception before incrementing, since the destructor
740
                // will not be called
741
0
                throw FactoryException("Too many recursive calls");
742
0
            }
743
307
            ++dbContext_->getPrivate()->recLevel_;
744
307
        }
745
746
307
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
747
748
      private:
749
        DatabaseContextNNPtr dbContext_;
750
    };
751
752
145
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
753
145
        return mapCanonicalizeGRFName_;
754
145
    }
755
756
    // cppcheck-suppress functionStatic
757
    common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
758
    // cppcheck-suppress functionStatic
759
    void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
760
761
    // cppcheck-suppress functionStatic
762
    crs::CRSPtr getCRSFromCache(const std::string &code);
763
    // cppcheck-suppress functionStatic
764
    void cache(const std::string &code, const crs::CRSNNPtr &crs);
765
766
    datum::GeodeticReferenceFramePtr
767
    // cppcheck-suppress functionStatic
768
    getGeodeticDatumFromCache(const std::string &code);
769
    // cppcheck-suppress functionStatic
770
    void cache(const std::string &code,
771
               const datum::GeodeticReferenceFrameNNPtr &datum);
772
773
    datum::DatumEnsemblePtr
774
    // cppcheck-suppress functionStatic
775
    getDatumEnsembleFromCache(const std::string &code);
776
    // cppcheck-suppress functionStatic
777
    void cache(const std::string &code,
778
               const datum::DatumEnsembleNNPtr &datumEnsemble);
779
780
    datum::EllipsoidPtr
781
    // cppcheck-suppress functionStatic
782
    getEllipsoidFromCache(const std::string &code);
783
    // cppcheck-suppress functionStatic
784
    void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
785
786
    datum::PrimeMeridianPtr
787
    // cppcheck-suppress functionStatic
788
    getPrimeMeridianFromCache(const std::string &code);
789
    // cppcheck-suppress functionStatic
790
    void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
791
792
    // cppcheck-suppress functionStatic
793
    cs::CoordinateSystemPtr
794
    getCoordinateSystemFromCache(const std::string &code);
795
    // cppcheck-suppress functionStatic
796
    void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
797
798
    // cppcheck-suppress functionStatic
799
    metadata::ExtentPtr getExtentFromCache(const std::string &code);
800
    // cppcheck-suppress functionStatic
801
    void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
802
803
    // cppcheck-suppress functionStatic
804
    bool getCRSToCRSCoordOpFromCache(
805
        const std::string &code,
806
        std::vector<operation::CoordinateOperationNNPtr> &list);
807
    // cppcheck-suppress functionStatic
808
    void cache(const std::string &code,
809
               const std::vector<operation::CoordinateOperationNNPtr> &list);
810
811
    struct GridInfoCache {
812
        std::string fullFilename{};
813
        std::string packageName{};
814
        std::string url{};
815
        bool found = false;
816
        bool directDownload = false;
817
        bool openLicense = false;
818
        bool gridAvailable = false;
819
    };
820
821
    // cppcheck-suppress functionStatic
822
    bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
823
    // cppcheck-suppress functionStatic
824
    void evictGridInfoFromCache(const std::string &code);
825
    // cppcheck-suppress functionStatic
826
    void cache(const std::string &code, const GridInfoCache &info);
827
828
    struct VersionedAuthName {
829
        std::string versionedAuthName{};
830
        std::string authName{};
831
        std::string version{};
832
        int priority = 0;
833
    };
834
    const std::vector<VersionedAuthName> &getCacheAuthNameWithVersion();
835
836
  private:
837
    friend class DatabaseContext;
838
839
    // This is a manual implementation of std::enable_shared_from_this<> that
840
    // avoids publicly deriving from it.
841
    std::weak_ptr<DatabaseContext> self_{};
842
843
    std::string databasePath_{};
844
    std::vector<std::string> auxiliaryDatabasePaths_{};
845
    std::shared_ptr<SQLiteHandle> sqlite_handle_{};
846
    unsigned int queryCounter_ = 0;
847
    std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
848
    PJ_CONTEXT *pjCtxt_ = nullptr;
849
    int recLevel_ = 0;
850
    bool detach_ = false;
851
    std::string lastMetadataValue_{};
852
    std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
853
854
    // Used by startInsertStatementsSession() and related functions
855
    std::string memoryDbForInsertPath_{};
856
    std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
857
858
    using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
859
860
    static constexpr size_t CACHE_SIZE = 128;
861
    LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
862
    LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
863
    LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
864
    LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
865
    LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE};
866
    LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
867
    LRUCacheOfObjects cacheCS_{CACHE_SIZE};
868
    LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
869
    lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
870
        cacheCRSToCrsCoordOp_{CACHE_SIZE};
871
    lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
872
873
    std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
874
875
    lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
876
        CACHE_SIZE};
877
    lru11::Cache<std::string, std::string> cacheNames_{CACHE_SIZE};
878
879
    std::vector<VersionedAuthName> cacheAuthNameWithVersion_{};
880
881
    static void insertIntoCache(LRUCacheOfObjects &cache,
882
                                const std::string &code,
883
                                const util::BaseObjectPtr &obj);
884
885
    static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
886
                             util::BaseObjectPtr &obj);
887
888
    void closeDB() noexcept;
889
890
    void clearCaches();
891
892
    std::string findFreeCode(const std::string &tableName,
893
                             const std::string &authName,
894
                             const std::string &codePrototype);
895
896
    void identify(const DatabaseContextNNPtr &dbContext,
897
                  const cs::CoordinateSystemNNPtr &obj, std::string &authName,
898
                  std::string &code);
899
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
900
                          const cs::CoordinateSystemNNPtr &obj,
901
                          const std::string &ownerType,
902
                          const std::string &ownerAuthName,
903
                          const std::string &ownerCode, std::string &authName,
904
                          std::string &code,
905
                          std::vector<std::string> &sqlStatements);
906
907
    void identify(const DatabaseContextNNPtr &dbContext,
908
                  const common::UnitOfMeasure &obj, std::string &authName,
909
                  std::string &code);
910
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
911
                          const common::UnitOfMeasure &unit,
912
                          const std::string &ownerAuthName,
913
                          std::string &authName, std::string &code,
914
                          std::vector<std::string> &sqlStatements);
915
916
    void appendSql(std::vector<std::string> &sqlStatements,
917
                   const std::string &sql);
918
919
    void
920
    identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj,
921
                           const std::string &tableName,
922
                           const std::string &authName, const std::string &code,
923
                           const std::vector<std::string> &allowedAuthorities,
924
                           std::vector<std::string> &sqlStatements);
925
926
    std::vector<std::string>
927
    getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm,
928
                           const std::string &authName, const std::string &code,
929
                           bool numericCode,
930
                           const std::vector<std::string> &allowedAuthorities);
931
932
    std::vector<std::string>
933
    getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid,
934
                           const std::string &authName, const std::string &code,
935
                           bool numericCode,
936
                           const std::vector<std::string> &allowedAuthorities);
937
938
    std::vector<std::string>
939
    getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum,
940
                           const std::string &authName, const std::string &code,
941
                           bool numericCode,
942
                           const std::vector<std::string> &allowedAuthorities);
943
944
    std::vector<std::string>
945
    getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble,
946
                           const std::string &authName, const std::string &code,
947
                           bool numericCode,
948
                           const std::vector<std::string> &allowedAuthorities);
949
950
    std::vector<std::string>
951
    getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs,
952
                           const std::string &authName, const std::string &code,
953
                           bool numericCode,
954
                           const std::vector<std::string> &allowedAuthorities);
955
956
    std::vector<std::string>
957
    getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs,
958
                           const std::string &authName, const std::string &code,
959
                           bool numericCode,
960
                           const std::vector<std::string> &allowedAuthorities);
961
962
    std::vector<std::string>
963
    getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum,
964
                           const std::string &authName, const std::string &code,
965
                           bool numericCode,
966
                           const std::vector<std::string> &allowedAuthorities);
967
968
    std::vector<std::string>
969
    getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs,
970
                           const std::string &authName, const std::string &code,
971
                           bool numericCode,
972
                           const std::vector<std::string> &allowedAuthorities);
973
974
    std::vector<std::string>
975
    getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs,
976
                           const std::string &authName, const std::string &code,
977
                           bool numericCode,
978
                           const std::vector<std::string> &allowedAuthorities);
979
980
    Private(const Private &) = delete;
981
    Private &operator=(const Private &) = delete;
982
};
983
984
// ---------------------------------------------------------------------------
985
986
9.83k
DatabaseContext::Private::Private() = default;
987
988
// ---------------------------------------------------------------------------
989
990
9.83k
DatabaseContext::Private::~Private() {
991
9.83k
    assert(recLevel_ == 0);
992
993
9.83k
    closeDB();
994
9.83k
}
995
996
// ---------------------------------------------------------------------------
997
998
9.83k
void DatabaseContext::Private::closeDB() noexcept {
999
1000
9.83k
    if (detach_) {
1001
        // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
1002
        // a crash in TEST(factory, attachExtraDatabases_auxiliary)
1003
        // due to possible wrong caching of key info.
1004
        // The bug is specific to using a memory file with shared cache as an
1005
        // auxiliary DB.
1006
        // The fix was likely in 3.8.8
1007
        // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
1008
        // But just after 3.8.2,
1009
        // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
1010
        // also seemed to hide the issue.
1011
        // Detaching a database hides the issue, not sure if it is by chance...
1012
0
        try {
1013
0
            run("DETACH DATABASE db_0");
1014
0
        } catch (...) {
1015
0
        }
1016
0
        detach_ = false;
1017
0
    }
1018
1019
81.8k
    for (auto &pair : mapSqlToStatement_) {
1020
81.8k
        sqlite3_finalize(pair.second);
1021
81.8k
    }
1022
9.83k
    mapSqlToStatement_.clear();
1023
1024
9.83k
    sqlite_handle_.reset();
1025
9.83k
}
1026
1027
// ---------------------------------------------------------------------------
1028
1029
0
void DatabaseContext::Private::clearCaches() {
1030
1031
0
    cacheUOM_.clear();
1032
0
    cacheCRS_.clear();
1033
0
    cacheEllipsoid_.clear();
1034
0
    cacheGeodeticDatum_.clear();
1035
0
    cacheDatumEnsemble_.clear();
1036
0
    cachePrimeMeridian_.clear();
1037
0
    cacheCS_.clear();
1038
0
    cacheExtent_.clear();
1039
0
    cacheCRSToCrsCoordOp_.clear();
1040
0
    cacheGridInfo_.clear();
1041
0
    cacheAllowedAuthorities_.clear();
1042
0
    cacheAliasNames_.clear();
1043
0
    cacheNames_.clear();
1044
0
}
1045
1046
// ---------------------------------------------------------------------------
1047
1048
1.15M
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
1049
1.15M
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
1050
1.15M
    if (sqlite_handle_ && !sqlite_handle_->isValid()) {
1051
0
        closeDB();
1052
0
        open(databasePath_, pjCtxt_);
1053
0
        if (!auxiliaryDatabasePaths_.empty()) {
1054
0
            attachExtraDatabases(auxiliaryDatabasePaths_);
1055
0
        }
1056
0
    }
1057
1.15M
#endif
1058
1.15M
    return sqlite_handle_;
1059
1.15M
}
1060
1061
// ---------------------------------------------------------------------------
1062
1063
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1064
                                               const std::string &code,
1065
136k
                                               const util::BaseObjectPtr &obj) {
1066
136k
    cache.insert(code, obj);
1067
136k
}
1068
1069
// ---------------------------------------------------------------------------
1070
1071
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1072
                                            const std::string &code,
1073
1.71M
                                            util::BaseObjectPtr &obj) {
1074
1.71M
    cache.tryGet(code, obj);
1075
1.71M
}
1076
1077
// ---------------------------------------------------------------------------
1078
1079
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1080
    const std::string &code,
1081
185k
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1082
185k
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1083
185k
}
1084
1085
// ---------------------------------------------------------------------------
1086
1087
void DatabaseContext::Private::cache(
1088
    const std::string &code,
1089
39.8k
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1090
39.8k
    cacheCRSToCrsCoordOp_.insert(code, list);
1091
39.8k
}
1092
1093
// ---------------------------------------------------------------------------
1094
1095
739k
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1096
739k
    util::BaseObjectPtr obj;
1097
739k
    getFromCache(cacheCRS_, code, obj);
1098
739k
    return std::static_pointer_cast<crs::CRS>(obj);
1099
739k
}
1100
1101
// ---------------------------------------------------------------------------
1102
1103
void DatabaseContext::Private::cache(const std::string &code,
1104
47.1k
                                     const crs::CRSNNPtr &crs) {
1105
47.1k
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1106
47.1k
}
1107
1108
// ---------------------------------------------------------------------------
1109
1110
common::UnitOfMeasurePtr
1111
115k
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1112
115k
    util::BaseObjectPtr obj;
1113
115k
    getFromCache(cacheUOM_, code, obj);
1114
115k
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1115
115k
}
1116
1117
// ---------------------------------------------------------------------------
1118
1119
void DatabaseContext::Private::cache(const std::string &code,
1120
15.5k
                                     const common::UnitOfMeasureNNPtr &uom) {
1121
15.5k
    insertIntoCache(cacheUOM_, code, uom.as_nullable());
1122
15.5k
}
1123
1124
// ---------------------------------------------------------------------------
1125
1126
datum::GeodeticReferenceFramePtr
1127
353k
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1128
353k
    util::BaseObjectPtr obj;
1129
353k
    getFromCache(cacheGeodeticDatum_, code, obj);
1130
353k
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1131
353k
}
1132
1133
// ---------------------------------------------------------------------------
1134
1135
void DatabaseContext::Private::cache(
1136
43.1k
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1137
43.1k
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1138
43.1k
}
1139
1140
// ---------------------------------------------------------------------------
1141
1142
datum::DatumEnsemblePtr
1143
377k
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1144
377k
    util::BaseObjectPtr obj;
1145
377k
    getFromCache(cacheDatumEnsemble_, code, obj);
1146
377k
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1147
377k
}
1148
1149
// ---------------------------------------------------------------------------
1150
1151
void DatabaseContext::Private::cache(
1152
3.25k
    const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) {
1153
3.25k
    insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable());
1154
3.25k
}
1155
1156
// ---------------------------------------------------------------------------
1157
1158
datum::EllipsoidPtr
1159
43.3k
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1160
43.3k
    util::BaseObjectPtr obj;
1161
43.3k
    getFromCache(cacheEllipsoid_, code, obj);
1162
43.3k
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1163
43.3k
}
1164
1165
// ---------------------------------------------------------------------------
1166
1167
void DatabaseContext::Private::cache(const std::string &code,
1168
7.39k
                                     const datum::EllipsoidNNPtr &ellps) {
1169
7.39k
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1170
7.39k
}
1171
1172
// ---------------------------------------------------------------------------
1173
1174
datum::PrimeMeridianPtr
1175
43.1k
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1176
43.1k
    util::BaseObjectPtr obj;
1177
43.1k
    getFromCache(cachePrimeMeridian_, code, obj);
1178
43.1k
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1179
43.1k
}
1180
1181
// ---------------------------------------------------------------------------
1182
1183
void DatabaseContext::Private::cache(const std::string &code,
1184
4.12k
                                     const datum::PrimeMeridianNNPtr &pm) {
1185
4.12k
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1186
4.12k
}
1187
1188
// ---------------------------------------------------------------------------
1189
1190
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1191
46.7k
    const std::string &code) {
1192
46.7k
    util::BaseObjectPtr obj;
1193
46.7k
    getFromCache(cacheCS_, code, obj);
1194
46.7k
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1195
46.7k
}
1196
1197
// ---------------------------------------------------------------------------
1198
1199
void DatabaseContext::Private::cache(const std::string &code,
1200
16.2k
                                     const cs::CoordinateSystemNNPtr &cs) {
1201
16.2k
    insertIntoCache(cacheCS_, code, cs.as_nullable());
1202
16.2k
}
1203
1204
// ---------------------------------------------------------------------------
1205
1206
metadata::ExtentPtr
1207
0
DatabaseContext::Private::getExtentFromCache(const std::string &code) {
1208
0
    util::BaseObjectPtr obj;
1209
0
    getFromCache(cacheExtent_, code, obj);
1210
0
    return std::static_pointer_cast<metadata::Extent>(obj);
1211
0
}
1212
1213
// ---------------------------------------------------------------------------
1214
1215
void DatabaseContext::Private::cache(const std::string &code,
1216
0
                                     const metadata::ExtentNNPtr &extent) {
1217
0
    insertIntoCache(cacheExtent_, code, extent.as_nullable());
1218
0
}
1219
1220
// ---------------------------------------------------------------------------
1221
1222
bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
1223
139k
                                                    GridInfoCache &info) {
1224
139k
    return cacheGridInfo_.tryGet(code, info);
1225
139k
}
1226
1227
// ---------------------------------------------------------------------------
1228
1229
0
void DatabaseContext::Private::evictGridInfoFromCache(const std::string &code) {
1230
0
    cacheGridInfo_.remove(code);
1231
0
}
1232
1233
// ---------------------------------------------------------------------------
1234
1235
void DatabaseContext::Private::cache(const std::string &code,
1236
40.2k
                                     const GridInfoCache &info) {
1237
40.2k
    cacheGridInfo_.insert(code, info);
1238
40.2k
}
1239
1240
// ---------------------------------------------------------------------------
1241
1242
void DatabaseContext::Private::open(const std::string &databasePath,
1243
9.83k
                                    PJ_CONTEXT *ctx) {
1244
9.83k
    if (!ctx) {
1245
0
        ctx = pj_get_default_ctx();
1246
0
    }
1247
1248
9.83k
    setPjCtxt(ctx);
1249
9.83k
    std::string path(databasePath);
1250
9.83k
    if (path.empty()) {
1251
9.83k
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
1252
9.83k
        path.resize(2048);
1253
9.83k
        const bool found =
1254
9.83k
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1,
1255
9.83k
                         /* disable_network = */ true) != 0;
1256
9.83k
        path.resize(strlen(path.c_str()));
1257
9.83k
        if (!found)
1258
0
#endif
1259
0
        {
1260
0
#ifdef EMBED_RESOURCE_FILES
1261
0
            path = EMBEDDED_PROJ_DB;
1262
#else
1263
            throw FactoryException("Cannot find proj.db");
1264
#endif
1265
0
        }
1266
9.83k
    }
1267
1268
9.83k
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1269
1270
9.83k
    databasePath_ = sqlite_handle_->path();
1271
9.83k
}
1272
1273
// ---------------------------------------------------------------------------
1274
1275
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1276
1277
0
    assert(sqlite_handle);
1278
0
    assert(!sqlite_handle_);
1279
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1280
0
}
1281
1282
// ---------------------------------------------------------------------------
1283
1284
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1285
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1286
0
                                           memoryDbForInsertPath_.empty()
1287
0
                                       ? ""
1288
0
                                       : "db_0.");
1289
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1290
0
                        "sqlite_master WHERE type = ");
1291
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1292
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1293
0
    std::vector<std::string> res;
1294
0
    for (const auto &objectType : objectTypes) {
1295
0
        const auto sqlRes = run(sqlBegin + objectType);
1296
0
        for (const auto &row : sqlRes) {
1297
0
            res.emplace_back(row[0]);
1298
0
        }
1299
0
    }
1300
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1301
0
        res.emplace_back(
1302
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1303
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1304
0
        res.emplace_back(
1305
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1306
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1307
0
    }
1308
0
    return res;
1309
0
}
1310
1311
// ---------------------------------------------------------------------------
1312
1313
void DatabaseContext::Private::attachExtraDatabases(
1314
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1315
1316
0
    auto l_handle = handle();
1317
0
    assert(l_handle);
1318
1319
0
    auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN "
1320
0
                      "('table', 'view') "
1321
0
                      "AND name NOT LIKE 'sqlite_stat%'");
1322
1323
0
    struct TableStructure {
1324
0
        std::string name{};
1325
0
        bool isTable = false;
1326
0
        std::string sql{};
1327
0
        std::vector<std::string> columns{};
1328
0
    };
1329
0
    std::vector<TableStructure> tablesStructure;
1330
0
    for (const auto &rowTable : tables) {
1331
0
        TableStructure tableStructure;
1332
0
        tableStructure.name = rowTable[0];
1333
0
        tableStructure.isTable = rowTable[1] == "table";
1334
0
        tableStructure.sql = rowTable[2];
1335
0
        auto tableInfo =
1336
0
            run("PRAGMA table_info(\"" +
1337
0
                replaceAll(tableStructure.name, "\"", "\"\"") + "\")");
1338
0
        for (const auto &rowCol : tableInfo) {
1339
0
            const auto &colName = rowCol[1];
1340
0
            tableStructure.columns.push_back(colName);
1341
0
        }
1342
0
        tablesStructure.push_back(std::move(tableStructure));
1343
0
    }
1344
1345
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1346
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1347
1348
0
    closeDB();
1349
0
    if (auxiliaryDatabasePaths.empty()) {
1350
0
        open(databasePath_, pjCtxt());
1351
0
        return;
1352
0
    }
1353
1354
0
    sqlite3 *sqlite_handle = nullptr;
1355
0
    sqlite3_open_v2(
1356
0
        ":memory:", &sqlite_handle,
1357
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1358
0
    if (!sqlite_handle) {
1359
0
        throw FactoryException("cannot create in memory database");
1360
0
    }
1361
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1362
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1363
0
    l_handle = sqlite_handle_;
1364
1365
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1366
0
    detach_ = true;
1367
0
    int count = 1;
1368
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1369
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1370
0
        std::string sql = "ATTACH DATABASE ? AS ";
1371
0
        sql += attachedDbName;
1372
0
        count++;
1373
0
        run(sql, {otherDbPath});
1374
1375
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1376
0
                                      attachedDbName + '.');
1377
0
    }
1378
1379
0
    for (const auto &tableStructure : tablesStructure) {
1380
0
        if (tableStructure.isTable) {
1381
0
            std::string sql("CREATE TEMP VIEW ");
1382
0
            sql += tableStructure.name;
1383
0
            sql += " AS ";
1384
0
            for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1385
0
                std::string selectFromAux("SELECT ");
1386
0
                bool firstCol = true;
1387
0
                for (const auto &colName : tableStructure.columns) {
1388
0
                    if (!firstCol) {
1389
0
                        selectFromAux += ", ";
1390
0
                    }
1391
0
                    firstCol = false;
1392
0
                    selectFromAux += colName;
1393
0
                }
1394
0
                selectFromAux += " FROM db_";
1395
0
                selectFromAux += toString(static_cast<int>(i));
1396
0
                selectFromAux += ".";
1397
0
                selectFromAux += tableStructure.name;
1398
1399
0
                try {
1400
                    // Check that the request will succeed. In case of 'sparse'
1401
                    // databases...
1402
0
                    run(selectFromAux + " LIMIT 0");
1403
1404
0
                    if (i > 0) {
1405
0
                        if (tableStructure.name == "conversion_method")
1406
0
                            sql += " UNION ";
1407
0
                        else
1408
0
                            sql += " UNION ALL ";
1409
0
                    }
1410
0
                    sql += selectFromAux;
1411
0
                } catch (const std::exception &) {
1412
0
                }
1413
0
            }
1414
0
            run(sql);
1415
0
        } else {
1416
0
            run(replaceAll(tableStructure.sql, "CREATE VIEW",
1417
0
                           "CREATE TEMP VIEW"));
1418
0
        }
1419
0
    }
1420
0
}
1421
1422
// ---------------------------------------------------------------------------
1423
1424
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1425
                                           const ListOfParams &parameters,
1426
1.15M
                                           bool useMaxFloatPrecision) {
1427
1428
1.15M
    auto l_handle = handle();
1429
1.15M
    assert(l_handle);
1430
1431
1.15M
    sqlite3_stmt *stmt = nullptr;
1432
1.15M
    auto iter = mapSqlToStatement_.find(sql);
1433
1.15M
    if (iter != mapSqlToStatement_.end()) {
1434
1.07M
        stmt = iter->second;
1435
1.07M
        sqlite3_reset(stmt);
1436
1.07M
    } else {
1437
81.8k
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1438
81.8k
                               static_cast<int>(sql.size()), &stmt,
1439
81.8k
                               nullptr) != SQLITE_OK) {
1440
0
            throw FactoryException(
1441
0
                std::string("SQLite error [ ")
1442
0
                    .append(sqlite3_errmsg(l_handle->handle()))
1443
0
                    .append(" ] on ")
1444
0
                    .append(sql));
1445
0
        }
1446
81.8k
        mapSqlToStatement_.insert(
1447
81.8k
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1448
81.8k
    }
1449
1450
1.15M
    ++queryCounter_;
1451
1452
1.15M
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1453
1.15M
}
1454
1455
// ---------------------------------------------------------------------------
1456
1457
0
static std::string formatStatement(const char *fmt, ...) {
1458
0
    std::string res;
1459
0
    va_list args;
1460
0
    va_start(args, fmt);
1461
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1462
0
        if (fmt[i] == '%') {
1463
0
            if (fmt[i + 1] == '%') {
1464
0
                res += '%';
1465
0
            } else if (fmt[i + 1] == 'q') {
1466
0
                const char *arg = va_arg(args, const char *);
1467
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1468
0
                    if (arg[j] == '\'')
1469
0
                        res += arg[j];
1470
0
                    res += arg[j];
1471
0
                }
1472
0
            } else if (fmt[i + 1] == 'Q') {
1473
0
                const char *arg = va_arg(args, const char *);
1474
0
                if (arg == nullptr)
1475
0
                    res += "NULL";
1476
0
                else {
1477
0
                    res += '\'';
1478
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1479
0
                        if (arg[j] == '\'')
1480
0
                            res += arg[j];
1481
0
                        res += arg[j];
1482
0
                    }
1483
0
                    res += '\'';
1484
0
                }
1485
0
            } else if (fmt[i + 1] == 's') {
1486
0
                const char *arg = va_arg(args, const char *);
1487
0
                res += arg;
1488
0
            } else if (fmt[i + 1] == 'f') {
1489
0
                const double arg = va_arg(args, double);
1490
0
                res += toString(arg);
1491
0
            } else if (fmt[i + 1] == 'd') {
1492
0
                const int arg = va_arg(args, int);
1493
0
                res += toString(arg);
1494
0
            } else {
1495
0
                va_end(args);
1496
0
                throw FactoryException(
1497
0
                    "Unsupported formatter in formatStatement()");
1498
0
            }
1499
0
            ++i;
1500
0
        } else {
1501
0
            res += fmt[i];
1502
0
        }
1503
0
    }
1504
0
    va_end(args);
1505
0
    return res;
1506
0
}
1507
1508
// ---------------------------------------------------------------------------
1509
1510
void DatabaseContext::Private::appendSql(
1511
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1512
0
    sqlStatements.emplace_back(sql);
1513
0
    char *errMsg = nullptr;
1514
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1515
0
                     &errMsg) != SQLITE_OK) {
1516
0
        std::string s("Cannot execute " + sql);
1517
0
        if (errMsg) {
1518
0
            s += " : ";
1519
0
            s += errMsg;
1520
0
        }
1521
0
        sqlite3_free(errMsg);
1522
0
        throw FactoryException(s);
1523
0
    }
1524
0
    sqlite3_free(errMsg);
1525
0
}
1526
1527
// ---------------------------------------------------------------------------
1528
1529
static void identifyFromNameOrCode(
1530
    const DatabaseContextNNPtr &dbContext,
1531
    const std::vector<std::string> &allowedAuthorities,
1532
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1533
    std::function<std::shared_ptr<util::IComparable>(
1534
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1535
        instantiateFunc,
1536
    AuthorityFactory::ObjectType objType, std::string &authName,
1537
0
    std::string &code) {
1538
1539
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1540
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1541
1542
0
    for (const auto &id : obj->identifiers()) {
1543
0
        try {
1544
0
            const auto &idAuthName = *(id->codeSpace());
1545
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1546
0
                          allowedAuthoritiesTmp.end(),
1547
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1548
0
                const auto factory =
1549
0
                    AuthorityFactory::create(dbContext, idAuthName);
1550
0
                if (instantiateFunc(factory, id->code())
1551
0
                        ->isEquivalentTo(
1552
0
                            obj.get(),
1553
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1554
0
                    authName = idAuthName;
1555
0
                    code = id->code();
1556
0
                    return;
1557
0
                }
1558
0
            }
1559
0
        } catch (const std::exception &) {
1560
0
        }
1561
0
    }
1562
1563
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1564
0
        const auto factory =
1565
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1566
0
        const auto candidates =
1567
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1568
0
        for (const auto &candidate : candidates) {
1569
0
            const auto &ids = candidate->identifiers();
1570
0
            if (!ids.empty() &&
1571
0
                candidate->isEquivalentTo(
1572
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1573
0
                const auto &id = ids.front();
1574
0
                authName = *(id->codeSpace());
1575
0
                code = id->code();
1576
0
                return;
1577
0
            }
1578
0
        }
1579
0
    }
1580
0
}
1581
1582
// ---------------------------------------------------------------------------
1583
1584
static void
1585
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1586
                       const std::vector<std::string> &allowedAuthorities,
1587
                       const std::string &authNameParent,
1588
                       const datum::DatumEnsembleNNPtr &obj,
1589
0
                       std::string &authName, std::string &code) {
1590
0
    const char *type = "geodetic_datum";
1591
0
    if (!obj->datums().empty() &&
1592
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1593
0
            obj->datums().front().get())) {
1594
0
        type = "vertical_datum";
1595
0
    }
1596
0
    const auto instantiateFunc =
1597
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1598
0
                const std::string &lCode) {
1599
0
            return util::nn_static_pointer_cast<util::IComparable>(
1600
0
                authFactory->createDatumEnsemble(lCode, type));
1601
0
        };
1602
0
    identifyFromNameOrCode(
1603
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1604
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1605
0
}
1606
1607
// ---------------------------------------------------------------------------
1608
1609
static void
1610
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1611
                       const std::vector<std::string> &allowedAuthorities,
1612
                       const std::string &authNameParent,
1613
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1614
0
                       std::string &authName, std::string &code) {
1615
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1616
0
                                    const std::string &lCode) {
1617
0
        return util::nn_static_pointer_cast<util::IComparable>(
1618
0
            authFactory->createGeodeticDatum(lCode));
1619
0
    };
1620
0
    identifyFromNameOrCode(
1621
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1622
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1623
0
}
1624
1625
// ---------------------------------------------------------------------------
1626
1627
static void
1628
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1629
                       const std::vector<std::string> &allowedAuthorities,
1630
                       const std::string &authNameParent,
1631
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1632
0
                       std::string &code) {
1633
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1634
0
                                    const std::string &lCode) {
1635
0
        return util::nn_static_pointer_cast<util::IComparable>(
1636
0
            authFactory->createEllipsoid(lCode));
1637
0
    };
1638
0
    identifyFromNameOrCode(
1639
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1640
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1641
0
}
1642
1643
// ---------------------------------------------------------------------------
1644
1645
static void
1646
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1647
                       const std::vector<std::string> &allowedAuthorities,
1648
                       const std::string &authNameParent,
1649
                       const datum::PrimeMeridianNNPtr &obj,
1650
0
                       std::string &authName, std::string &code) {
1651
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1652
0
                                    const std::string &lCode) {
1653
0
        return util::nn_static_pointer_cast<util::IComparable>(
1654
0
            authFactory->createPrimeMeridian(lCode));
1655
0
    };
1656
0
    identifyFromNameOrCode(
1657
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1658
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1659
0
}
1660
1661
// ---------------------------------------------------------------------------
1662
1663
static void
1664
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1665
                       const std::vector<std::string> &allowedAuthorities,
1666
                       const std::string &authNameParent,
1667
                       const datum::VerticalReferenceFrameNNPtr &obj,
1668
0
                       std::string &authName, std::string &code) {
1669
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1670
0
                                    const std::string &lCode) {
1671
0
        return util::nn_static_pointer_cast<util::IComparable>(
1672
0
            authFactory->createVerticalDatum(lCode));
1673
0
    };
1674
0
    identifyFromNameOrCode(
1675
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1676
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1677
0
}
1678
1679
// ---------------------------------------------------------------------------
1680
1681
static void
1682
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1683
                       const std::vector<std::string> &allowedAuthorities,
1684
                       const std::string &authNameParent,
1685
                       const datum::DatumNNPtr &obj, std::string &authName,
1686
0
                       std::string &code) {
1687
0
    if (const auto geodeticDatum =
1688
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1689
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1690
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1691
0
    } else if (const auto verticalDatum =
1692
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1693
0
                       obj)) {
1694
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1695
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1696
0
    } else {
1697
0
        throw FactoryException("Unhandled type of datum");
1698
0
    }
1699
0
}
1700
1701
// ---------------------------------------------------------------------------
1702
1703
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1704
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1705
0
        return CS_TYPE_ELLIPSOIDAL;
1706
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1707
0
        return CS_TYPE_CARTESIAN;
1708
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1709
0
        return CS_TYPE_VERTICAL;
1710
0
    }
1711
0
    return nullptr;
1712
0
}
1713
1714
// ---------------------------------------------------------------------------
1715
1716
std::string
1717
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1718
                                       const std::string &authName,
1719
0
                                       const std::string &codePrototype) {
1720
0
    std::string code(codePrototype);
1721
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1722
0
            {authName, code})
1723
0
            .empty()) {
1724
0
        return code;
1725
0
    }
1726
1727
0
    for (int counter = 2; counter < 10; counter++) {
1728
0
        code = codePrototype + '_' + toString(counter);
1729
0
        if (run("SELECT 1 FROM " + tableName +
1730
0
                    " WHERE auth_name = ? AND code = ?",
1731
0
                {authName, code})
1732
0
                .empty()) {
1733
0
            return code;
1734
0
        }
1735
0
    }
1736
1737
    // shouldn't happen hopefully...
1738
0
    throw FactoryException("Cannot insert " + tableName +
1739
0
                           ": too many similar codes");
1740
0
}
1741
1742
// ---------------------------------------------------------------------------
1743
1744
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1745
0
    switch (unit.type()) {
1746
0
    case common::UnitOfMeasure::Type::LINEAR:
1747
0
        return "length";
1748
1749
0
    case common::UnitOfMeasure::Type::ANGULAR:
1750
0
        return "angle";
1751
1752
0
    case common::UnitOfMeasure::Type::SCALE:
1753
0
        return "scale";
1754
1755
0
    case common::UnitOfMeasure::Type::TIME:
1756
0
        return "time";
1757
1758
0
    default:
1759
0
        break;
1760
0
    }
1761
0
    return nullptr;
1762
0
}
1763
1764
// ---------------------------------------------------------------------------
1765
1766
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1767
                                        const common::UnitOfMeasure &obj,
1768
                                        std::string &authName,
1769
0
                                        std::string &code) {
1770
    // Identify quickly a few well-known units
1771
0
    const double convFactor = obj.conversionToSI();
1772
0
    switch (obj.type()) {
1773
0
    case common::UnitOfMeasure::Type::LINEAR: {
1774
0
        if (convFactor == 1.0) {
1775
0
            authName = metadata::Identifier::EPSG;
1776
0
            code = "9001";
1777
0
            return;
1778
0
        }
1779
0
        break;
1780
0
    }
1781
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1782
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1783
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1784
0
            1e-10 * CONV_FACTOR_DEGREE) {
1785
0
            authName = metadata::Identifier::EPSG;
1786
0
            code = "9102";
1787
0
            return;
1788
0
        }
1789
0
        break;
1790
0
    }
1791
0
    case common::UnitOfMeasure::Type::SCALE: {
1792
0
        if (convFactor == 1.0) {
1793
0
            authName = metadata::Identifier::EPSG;
1794
0
            code = "9201";
1795
0
            return;
1796
0
        }
1797
0
        break;
1798
0
    }
1799
0
    default:
1800
0
        break;
1801
0
    }
1802
1803
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1804
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1805
0
    ListOfParams params{convFactor};
1806
0
    const char *type = getUnitDatabaseType(obj);
1807
0
    if (type) {
1808
0
        sql += " AND type = ?";
1809
0
        params.emplace_back(std::string(type));
1810
0
    }
1811
0
    sql += " ORDER BY auth_name, code";
1812
0
    const auto res = run(sql, params);
1813
0
    for (const auto &row : res) {
1814
0
        const auto &rowAuthName = row[0];
1815
0
        const auto &rowCode = row[1];
1816
0
        const auto tmpAuthFactory =
1817
0
            AuthorityFactory::create(dbContext, rowAuthName);
1818
0
        try {
1819
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1820
0
            authName = rowAuthName;
1821
0
            code = rowCode;
1822
0
            return;
1823
0
        } catch (const std::exception &) {
1824
0
        }
1825
0
    }
1826
0
}
1827
1828
// ---------------------------------------------------------------------------
1829
1830
void DatabaseContext::Private::identifyOrInsert(
1831
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1832
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1833
0
    std::vector<std::string> &sqlStatements) {
1834
0
    authName = unit.codeSpace();
1835
0
    code = unit.code();
1836
0
    if (authName.empty()) {
1837
0
        identify(dbContext, unit, authName, code);
1838
0
    }
1839
0
    if (!authName.empty()) {
1840
0
        return;
1841
0
    }
1842
0
    const char *type = getUnitDatabaseType(unit);
1843
0
    if (type == nullptr) {
1844
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1845
0
    }
1846
1847
    // Insert new record
1848
0
    authName = ownerAuthName;
1849
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1850
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1851
1852
0
    const auto sql = formatStatement(
1853
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1854
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1855
0
        unit.conversionToSI());
1856
0
    appendSql(sqlStatements, sql);
1857
0
}
1858
1859
// ---------------------------------------------------------------------------
1860
1861
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1862
                                        const cs::CoordinateSystemNNPtr &obj,
1863
                                        std::string &authName,
1864
0
                                        std::string &code) {
1865
1866
0
    const auto &axisList = obj->axisList();
1867
0
    if (axisList.size() == 1U &&
1868
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1869
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1870
0
        (axisList[0]->nameStr() == "Up" ||
1871
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1872
        // preferred coordinate system for gravity-related height
1873
0
        authName = metadata::Identifier::EPSG;
1874
0
        code = "6499";
1875
0
        return;
1876
0
    }
1877
1878
0
    std::string sql(
1879
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1880
0
    ListOfParams params{static_cast<int>(axisList.size())};
1881
0
    const char *type = getCSDatabaseType(obj);
1882
0
    if (type) {
1883
0
        sql += " AND type = ?";
1884
0
        params.emplace_back(std::string(type));
1885
0
    }
1886
0
    sql += " ORDER BY auth_name, code";
1887
0
    const auto res = run(sql, params);
1888
0
    for (const auto &row : res) {
1889
0
        const auto &rowAuthName = row[0];
1890
0
        const auto &rowCode = row[1];
1891
0
        const auto tmpAuthFactory =
1892
0
            AuthorityFactory::create(dbContext, rowAuthName);
1893
0
        try {
1894
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1895
0
            if (cs->_isEquivalentTo(obj.get(),
1896
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1897
0
                authName = rowAuthName;
1898
0
                code = rowCode;
1899
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1900
                    // preferred coordinate system for cartesian
1901
                    // Easting, Northing
1902
0
                    return;
1903
0
                }
1904
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1905
                    // preferred coordinate system for geographic lat, long
1906
0
                    return;
1907
0
                }
1908
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1909
                    // preferred coordinate system for geographic lat, long, h
1910
0
                    return;
1911
0
                }
1912
0
            }
1913
0
        } catch (const std::exception &) {
1914
0
        }
1915
0
    }
1916
0
}
1917
1918
// ---------------------------------------------------------------------------
1919
1920
void DatabaseContext::Private::identifyOrInsert(
1921
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1922
    const std::string &ownerType, const std::string &ownerAuthName,
1923
    const std::string &ownerCode, std::string &authName, std::string &code,
1924
0
    std::vector<std::string> &sqlStatements) {
1925
1926
0
    identify(dbContext, obj, authName, code);
1927
0
    if (!authName.empty()) {
1928
0
        return;
1929
0
    }
1930
1931
0
    const char *type = getCSDatabaseType(obj);
1932
0
    if (type == nullptr) {
1933
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1934
0
    }
1935
1936
    // Insert new record in coordinate_system
1937
0
    authName = ownerAuthName;
1938
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1939
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1940
1941
0
    const auto &axisList = obj->axisList();
1942
0
    {
1943
0
        const auto sql = formatStatement(
1944
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1945
0
            authName.c_str(), code.c_str(), type,
1946
0
            static_cast<int>(axisList.size()));
1947
0
        appendSql(sqlStatements, sql);
1948
0
    }
1949
1950
    // Insert new records for the axis
1951
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1952
0
        const auto &axis = axisList[i];
1953
0
        std::string uomAuthName;
1954
0
        std::string uomCode;
1955
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1956
0
                         uomCode, sqlStatements);
1957
0
        const auto sql = formatStatement(
1958
0
            "INSERT INTO axis VALUES("
1959
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1960
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1961
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1962
0
            axis->direction().toString().c_str(), authName.c_str(),
1963
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1964
0
        appendSql(sqlStatements, sql);
1965
0
    }
1966
0
}
1967
1968
// ---------------------------------------------------------------------------
1969
1970
static void
1971
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1972
                          const std::string &authName, std::string &sql,
1973
0
                          ListOfParams &params) {
1974
0
    sql += "auth_name IN (?";
1975
0
    params.emplace_back(authName);
1976
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1977
0
        sql += ",?";
1978
0
        params.emplace_back(allowedAuthority);
1979
0
    }
1980
0
    sql += ')';
1981
0
}
1982
1983
// ---------------------------------------------------------------------------
1984
1985
void DatabaseContext::Private::identifyOrInsertUsages(
1986
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1987
    const std::string &authName, const std::string &code,
1988
    const std::vector<std::string> &allowedAuthorities,
1989
0
    std::vector<std::string> &sqlStatements) {
1990
1991
0
    std::string usageCode("USAGE_");
1992
0
    const std::string upperTableName(toupper(tableName));
1993
0
    if (!starts_with(code, upperTableName)) {
1994
0
        usageCode += upperTableName;
1995
0
        usageCode += '_';
1996
0
    }
1997
0
    usageCode += code;
1998
1999
0
    const auto &domains = obj->domains();
2000
0
    if (domains.empty()) {
2001
0
        const auto sql =
2002
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2003
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
2004
0
                            authName.c_str(), usageCode.c_str(),
2005
0
                            tableName.c_str(), authName.c_str(), code.c_str());
2006
0
        appendSql(sqlStatements, sql);
2007
0
        return;
2008
0
    }
2009
2010
0
    int usageCounter = 1;
2011
0
    for (const auto &domain : domains) {
2012
0
        std::string scopeAuthName;
2013
0
        std::string scopeCode;
2014
0
        const auto &scope = domain->scope();
2015
0
        if (scope.has_value()) {
2016
0
            std::string sql =
2017
0
                "SELECT auth_name, code, "
2018
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2019
0
                "AS order_idx "
2020
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
2021
0
            ListOfParams params{*scope};
2022
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2023
0
                                      params);
2024
0
            sql += " ORDER BY order_idx, auth_name, code";
2025
0
            const auto rows = run(sql, params);
2026
0
            if (!rows.empty()) {
2027
0
                const auto &row = rows.front();
2028
0
                scopeAuthName = row[0];
2029
0
                scopeCode = row[1];
2030
0
            } else {
2031
0
                scopeAuthName = authName;
2032
0
                scopeCode = "SCOPE_";
2033
0
                scopeCode += tableName;
2034
0
                scopeCode += '_';
2035
0
                scopeCode += code;
2036
0
                const auto sqlToInsert = formatStatement(
2037
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
2038
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
2039
0
                appendSql(sqlStatements, sqlToInsert);
2040
0
            }
2041
0
        } else {
2042
0
            scopeAuthName = "PROJ";
2043
0
            scopeCode = "SCOPE_UNKNOWN";
2044
0
        }
2045
2046
0
        std::string extentAuthName("PROJ");
2047
0
        std::string extentCode("EXTENT_UNKNOWN");
2048
0
        const auto &extent = domain->domainOfValidity();
2049
0
        if (extent) {
2050
0
            const auto &geogElts = extent->geographicElements();
2051
0
            if (!geogElts.empty()) {
2052
0
                const auto bbox =
2053
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
2054
0
                        geogElts.front().get());
2055
0
                if (bbox) {
2056
0
                    std::string sql =
2057
0
                        "SELECT auth_name, code, "
2058
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2059
0
                        "AS order_idx "
2060
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
2061
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
2062
0
                        "AND ";
2063
0
                    ListOfParams params{
2064
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
2065
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
2066
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2067
0
                                              params);
2068
0
                    sql += " ORDER BY order_idx, auth_name, code";
2069
0
                    const auto rows = run(sql, params);
2070
0
                    if (!rows.empty()) {
2071
0
                        const auto &row = rows.front();
2072
0
                        extentAuthName = row[0];
2073
0
                        extentCode = row[1];
2074
0
                    } else {
2075
0
                        extentAuthName = authName;
2076
0
                        extentCode = "EXTENT_";
2077
0
                        extentCode += tableName;
2078
0
                        extentCode += '_';
2079
0
                        extentCode += code;
2080
0
                        std::string description(*(extent->description()));
2081
0
                        if (description.empty()) {
2082
0
                            description = "unknown";
2083
0
                        }
2084
0
                        const auto sqlToInsert = formatStatement(
2085
0
                            "INSERT INTO extent "
2086
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
2087
0
                            extentAuthName.c_str(), extentCode.c_str(),
2088
0
                            description.c_str(), description.c_str(),
2089
0
                            bbox->southBoundLatitude(),
2090
0
                            bbox->northBoundLatitude(),
2091
0
                            bbox->westBoundLongitude(),
2092
0
                            bbox->eastBoundLongitude());
2093
0
                        appendSql(sqlStatements, sqlToInsert);
2094
0
                    }
2095
0
                }
2096
0
            }
2097
0
        }
2098
2099
0
        if (domains.size() > 1) {
2100
0
            usageCode += '_';
2101
0
            usageCode += toString(usageCounter);
2102
0
        }
2103
0
        const auto sql = formatStatement(
2104
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2105
0
            "'%q','%q','%q','%q');",
2106
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2107
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2108
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2109
0
        appendSql(sqlStatements, sql);
2110
2111
0
        usageCounter++;
2112
0
    }
2113
0
}
2114
2115
// ---------------------------------------------------------------------------
2116
2117
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2118
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2119
    const std::string &code, bool /*numericCode*/,
2120
0
    const std::vector<std::string> &allowedAuthorities) {
2121
2122
0
    const auto self = NN_NO_CHECK(self_.lock());
2123
2124
    // Check if the object is already known under that code
2125
0
    std::string pmAuthName;
2126
0
    std::string pmCode;
2127
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2128
0
                           pmCode);
2129
0
    if (pmAuthName == authName && pmCode == code) {
2130
0
        return {};
2131
0
    }
2132
2133
0
    std::vector<std::string> sqlStatements;
2134
2135
    // Insert new record in prime_meridian table
2136
0
    std::string uomAuthName;
2137
0
    std::string uomCode;
2138
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2139
0
                     uomCode, sqlStatements);
2140
2141
0
    const auto sql = formatStatement(
2142
0
        "INSERT INTO prime_meridian VALUES("
2143
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2144
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2145
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2146
0
    appendSql(sqlStatements, sql);
2147
2148
0
    return sqlStatements;
2149
0
}
2150
2151
// ---------------------------------------------------------------------------
2152
2153
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2154
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2155
    const std::string &code, bool /*numericCode*/,
2156
0
    const std::vector<std::string> &allowedAuthorities) {
2157
2158
0
    const auto self = NN_NO_CHECK(self_.lock());
2159
2160
    // Check if the object is already known under that code
2161
0
    std::string ellipsoidAuthName;
2162
0
    std::string ellipsoidCode;
2163
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2164
0
                           ellipsoidAuthName, ellipsoidCode);
2165
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2166
0
        return {};
2167
0
    }
2168
2169
0
    std::vector<std::string> sqlStatements;
2170
2171
    // Find or insert celestial body
2172
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2173
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2174
0
    constexpr double tolerance = 0.005;
2175
0
    std::string bodyAuthName;
2176
0
    std::string bodyCode;
2177
0
    auto res = run("SELECT auth_name, code, "
2178
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2179
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2180
0
                   {semiMajorAxisMetre, tolerance});
2181
0
    if (!res.empty()) {
2182
0
        const auto &row = res.front();
2183
0
        bodyAuthName = row[0];
2184
0
        bodyCode = row[1];
2185
0
    } else {
2186
0
        bodyAuthName = authName;
2187
0
        bodyCode = "BODY_" + code;
2188
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2189
0
        const auto sql = formatStatement(
2190
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2191
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2192
0
            semiMajorAxisMetre);
2193
0
        appendSql(sqlStatements, sql);
2194
0
    }
2195
2196
    // Insert new record in ellipsoid table
2197
0
    std::string uomAuthName;
2198
0
    std::string uomCode;
2199
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2200
0
                     sqlStatements);
2201
0
    std::string invFlattening("NULL");
2202
0
    std::string semiMinorAxis("NULL");
2203
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2204
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2205
0
    } else {
2206
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2207
0
    }
2208
2209
0
    const auto sql = formatStatement(
2210
0
        "INSERT INTO ellipsoid VALUES("
2211
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2212
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2213
0
        "", // description
2214
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2215
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2216
0
        semiMinorAxis.c_str());
2217
0
    appendSql(sqlStatements, sql);
2218
2219
0
    return sqlStatements;
2220
0
}
2221
2222
// ---------------------------------------------------------------------------
2223
2224
0
static std::string anchorEpochToStr(double val) {
2225
0
    constexpr int BUF_SIZE = 32;
2226
0
    char szBuffer[BUF_SIZE];
2227
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2228
0
    return szBuffer;
2229
0
}
2230
2231
// ---------------------------------------------------------------------------
2232
2233
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2234
    const datum::GeodeticReferenceFrameNNPtr &datum,
2235
    const std::string &authName, const std::string &code, bool numericCode,
2236
0
    const std::vector<std::string> &allowedAuthorities) {
2237
2238
0
    const auto self = NN_NO_CHECK(self_.lock());
2239
2240
    // Check if the object is already known under that code
2241
0
    std::string datumAuthName;
2242
0
    std::string datumCode;
2243
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2244
0
                           datumAuthName, datumCode);
2245
0
    if (datumAuthName == authName && datumCode == code) {
2246
0
        return {};
2247
0
    }
2248
2249
0
    std::vector<std::string> sqlStatements;
2250
2251
    // Find or insert ellipsoid
2252
0
    std::string ellipsoidAuthName;
2253
0
    std::string ellipsoidCode;
2254
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2255
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2256
0
                           ellipsoidAuthName, ellipsoidCode);
2257
0
    if (ellipsoidAuthName.empty()) {
2258
0
        ellipsoidAuthName = authName;
2259
0
        if (numericCode) {
2260
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2261
0
                                                  ellipsoidAuthName, true);
2262
0
        } else {
2263
0
            ellipsoidCode = "ELLPS_" + code;
2264
0
        }
2265
0
        sqlStatements = self->getInsertStatementsFor(
2266
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2267
0
            allowedAuthorities);
2268
0
    }
2269
2270
    // Find or insert prime meridian
2271
0
    std::string pmAuthName;
2272
0
    std::string pmCode;
2273
0
    const auto &pmOfDatum = datum->primeMeridian();
2274
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2275
0
                           pmAuthName, pmCode);
2276
0
    if (pmAuthName.empty()) {
2277
0
        pmAuthName = authName;
2278
0
        if (numericCode) {
2279
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2280
0
        } else {
2281
0
            pmCode = "PM_" + code;
2282
0
        }
2283
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2284
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2285
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2286
0
                             sqlStatementsTmp.end());
2287
0
    }
2288
2289
    // Insert new record in geodetic_datum table
2290
0
    std::string publicationDate("NULL");
2291
0
    if (datum->publicationDate().has_value()) {
2292
0
        publicationDate = '\'';
2293
0
        publicationDate +=
2294
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2295
0
        publicationDate += '\'';
2296
0
    }
2297
0
    std::string frameReferenceEpoch("NULL");
2298
0
    const auto dynamicDatum =
2299
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2300
0
    if (dynamicDatum) {
2301
0
        frameReferenceEpoch =
2302
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2303
0
    }
2304
0
    const std::string anchor(*(datum->anchorDefinition()));
2305
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2306
0
    const auto sql = formatStatement(
2307
0
        "INSERT INTO geodetic_datum VALUES("
2308
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2309
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2310
0
        "", // description
2311
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2312
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
2313
0
        anchor.empty() ? nullptr : anchor.c_str(),
2314
0
        anchorEpoch.has_value()
2315
0
            ? anchorEpochToStr(
2316
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2317
0
                  .c_str()
2318
0
            : "NULL");
2319
0
    appendSql(sqlStatements, sql);
2320
2321
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2322
0
                           allowedAuthorities, sqlStatements);
2323
2324
0
    return sqlStatements;
2325
0
}
2326
2327
// ---------------------------------------------------------------------------
2328
2329
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2330
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2331
    const std::string &code, bool numericCode,
2332
0
    const std::vector<std::string> &allowedAuthorities) {
2333
0
    const auto self = NN_NO_CHECK(self_.lock());
2334
2335
    // Check if the object is already known under that code
2336
0
    std::string datumAuthName;
2337
0
    std::string datumCode;
2338
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2339
0
                           datumAuthName, datumCode);
2340
0
    if (datumAuthName == authName && datumCode == code) {
2341
0
        return {};
2342
0
    }
2343
2344
0
    std::vector<std::string> sqlStatements;
2345
2346
0
    const auto &members = ensemble->datums();
2347
0
    assert(!members.empty());
2348
2349
0
    int counter = 1;
2350
0
    std::vector<std::pair<std::string, std::string>> membersId;
2351
0
    for (const auto &member : members) {
2352
0
        std::string memberAuthName;
2353
0
        std::string memberCode;
2354
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2355
0
                               memberAuthName, memberCode);
2356
0
        if (memberAuthName.empty()) {
2357
0
            memberAuthName = authName;
2358
0
            if (numericCode) {
2359
0
                memberCode =
2360
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2361
0
            } else {
2362
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2363
0
            }
2364
0
            const auto sqlStatementsTmp =
2365
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2366
0
                                             numericCode, allowedAuthorities);
2367
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2368
0
                                 sqlStatementsTmp.end());
2369
0
        }
2370
2371
0
        membersId.emplace_back(
2372
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2373
2374
0
        ++counter;
2375
0
    }
2376
2377
0
    const bool isGeodetic =
2378
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2379
0
            members.front()) != nullptr;
2380
2381
    // Insert new record in geodetic_datum/vertical_datum table
2382
0
    const double accuracy =
2383
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2384
0
    if (isGeodetic) {
2385
0
        const auto firstDatum =
2386
0
            AuthorityFactory::create(self, membersId.front().first)
2387
0
                ->createGeodeticDatum(membersId.front().second);
2388
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2389
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2390
0
        assert(!ellipsoidIds.empty());
2391
0
        const std::string &ellipsoidAuthName =
2392
0
            *(ellipsoidIds.front()->codeSpace());
2393
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2394
0
        const auto &pm = firstDatum->primeMeridian();
2395
0
        const auto &pmIds = pm->identifiers();
2396
0
        assert(!pmIds.empty());
2397
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2398
0
        const std::string &pmCode = pmIds.front()->code();
2399
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2400
0
        const util::optional<common::Measure> &anchorEpoch =
2401
0
            firstDatum->anchorEpoch();
2402
0
        const auto sql = formatStatement(
2403
0
            "INSERT INTO geodetic_datum VALUES("
2404
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2405
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2406
0
            "", // description
2407
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2408
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2409
0
            anchor.empty() ? nullptr : anchor.c_str(),
2410
0
            anchorEpoch.has_value()
2411
0
                ? anchorEpochToStr(
2412
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2413
0
                      .c_str()
2414
0
                : "NULL");
2415
0
        appendSql(sqlStatements, sql);
2416
0
    } else {
2417
0
        const auto firstDatum =
2418
0
            AuthorityFactory::create(self, membersId.front().first)
2419
0
                ->createVerticalDatum(membersId.front().second);
2420
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2421
0
        const util::optional<common::Measure> &anchorEpoch =
2422
0
            firstDatum->anchorEpoch();
2423
0
        const auto sql = formatStatement(
2424
0
            "INSERT INTO vertical_datum VALUES("
2425
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2426
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2427
0
            "", // description
2428
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2429
0
            anchorEpoch.has_value()
2430
0
                ? anchorEpochToStr(
2431
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2432
0
                      .c_str()
2433
0
                : "NULL");
2434
0
        appendSql(sqlStatements, sql);
2435
0
    }
2436
0
    identifyOrInsertUsages(ensemble,
2437
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2438
0
                           authName, code, allowedAuthorities, sqlStatements);
2439
2440
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2441
0
                                       : "vertical_datum_ensemble_member";
2442
0
    counter = 1;
2443
0
    for (const auto &authCodePair : membersId) {
2444
0
        const auto sql = formatStatement(
2445
0
            "INSERT INTO %s VALUES("
2446
0
            "'%q','%q','%q','%q',%d);",
2447
0
            tableName, authName.c_str(), code.c_str(),
2448
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2449
0
        appendSql(sqlStatements, sql);
2450
0
        ++counter;
2451
0
    }
2452
2453
0
    return sqlStatements;
2454
0
}
2455
2456
// ---------------------------------------------------------------------------
2457
2458
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2459
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2460
    const std::string &code, bool numericCode,
2461
0
    const std::vector<std::string> &allowedAuthorities) {
2462
2463
0
    const auto self = NN_NO_CHECK(self_.lock());
2464
2465
0
    std::vector<std::string> sqlStatements;
2466
2467
    // Find or insert datum/datum ensemble
2468
0
    std::string datumAuthName;
2469
0
    std::string datumCode;
2470
0
    const auto &ensemble = crs->datumEnsemble();
2471
0
    if (ensemble) {
2472
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2473
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2474
0
                               datumAuthName, datumCode);
2475
0
        if (datumAuthName.empty()) {
2476
0
            datumAuthName = authName;
2477
0
            if (numericCode) {
2478
0
                datumCode =
2479
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2480
0
            } else {
2481
0
                datumCode = "GEODETIC_DATUM_" + code;
2482
0
            }
2483
0
            sqlStatements = self->getInsertStatementsFor(
2484
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2485
0
                allowedAuthorities);
2486
0
        }
2487
0
    } else {
2488
0
        const auto &datum = crs->datum();
2489
0
        assert(datum);
2490
0
        const auto datumNN = NN_NO_CHECK(datum);
2491
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2492
0
                               datumAuthName, datumCode);
2493
0
        if (datumAuthName.empty()) {
2494
0
            datumAuthName = authName;
2495
0
            if (numericCode) {
2496
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2497
0
            } else {
2498
0
                datumCode = "GEODETIC_DATUM_" + code;
2499
0
            }
2500
0
            sqlStatements =
2501
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2502
0
                                             numericCode, allowedAuthorities);
2503
0
        }
2504
0
    }
2505
2506
    // Find or insert coordinate system
2507
0
    const auto &coordinateSystem = crs->coordinateSystem();
2508
0
    std::string csAuthName;
2509
0
    std::string csCode;
2510
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2511
0
                     csAuthName, csCode, sqlStatements);
2512
2513
0
    const char *type = CRS_SUBTYPE_GEOG_2D;
2514
0
    if (coordinateSystem->axisList().size() == 3) {
2515
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2516
0
            type = CRS_SUBTYPE_GEOG_3D;
2517
0
        } else {
2518
0
            type = CRS_SUBTYPE_GEOCENTRIC;
2519
0
        }
2520
0
    }
2521
2522
    // Insert new record in geodetic_crs table
2523
0
    const auto sql =
2524
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2525
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2526
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2527
0
                        "", // description
2528
0
                        type, csAuthName.c_str(), csCode.c_str(),
2529
0
                        datumAuthName.c_str(), datumCode.c_str());
2530
0
    appendSql(sqlStatements, sql);
2531
2532
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2533
0
                           allowedAuthorities, sqlStatements);
2534
0
    return sqlStatements;
2535
0
}
2536
2537
// ---------------------------------------------------------------------------
2538
2539
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2540
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2541
    const std::string &code, bool numericCode,
2542
0
    const std::vector<std::string> &allowedAuthorities) {
2543
2544
0
    const auto self = NN_NO_CHECK(self_.lock());
2545
2546
0
    std::vector<std::string> sqlStatements;
2547
2548
    // Find or insert base geodetic CRS
2549
0
    const auto &baseCRS = crs->baseCRS();
2550
0
    std::string geodAuthName;
2551
0
    std::string geodCode;
2552
2553
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2554
0
    allowedAuthoritiesTmp.emplace_back(authName);
2555
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2556
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2557
0
        const auto candidates = baseCRS->identify(factory);
2558
0
        for (const auto &candidate : candidates) {
2559
0
            if (candidate.second == 100) {
2560
0
                const auto &ids = candidate.first->identifiers();
2561
0
                if (!ids.empty()) {
2562
0
                    const auto &id = ids.front();
2563
0
                    geodAuthName = *(id->codeSpace());
2564
0
                    geodCode = id->code();
2565
0
                    break;
2566
0
                }
2567
0
            }
2568
0
            if (!geodAuthName.empty()) {
2569
0
                break;
2570
0
            }
2571
0
        }
2572
0
    }
2573
0
    if (geodAuthName.empty()) {
2574
0
        geodAuthName = authName;
2575
0
        geodCode = "GEODETIC_CRS_" + code;
2576
0
        sqlStatements = self->getInsertStatementsFor(
2577
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2578
0
    }
2579
2580
    // Insert new record in conversion table
2581
0
    const auto &conversion = crs->derivingConversionRef();
2582
0
    std::string convAuthName(authName);
2583
0
    std::string convCode("CONVERSION_" + code);
2584
0
    if (numericCode) {
2585
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2586
0
    }
2587
0
    {
2588
0
        const auto &method = conversion->method();
2589
0
        const auto &methodIds = method->identifiers();
2590
0
        std::string methodAuthName;
2591
0
        std::string methodCode;
2592
0
        const operation::MethodMapping *methodMapping = nullptr;
2593
0
        if (methodIds.empty()) {
2594
0
            const int epsgCode = method->getEPSGCode();
2595
0
            if (epsgCode > 0) {
2596
0
                methodAuthName = metadata::Identifier::EPSG;
2597
0
                methodCode = toString(epsgCode);
2598
0
            } else {
2599
0
                const auto &methodName = method->nameStr();
2600
0
                size_t nProjectionMethodMappings = 0;
2601
0
                const auto projectionMethodMappings =
2602
0
                    operation::getProjectionMethodMappings(
2603
0
                        nProjectionMethodMappings);
2604
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2605
0
                    const auto &mapping = projectionMethodMappings[i];
2606
0
                    if (metadata::Identifier::isEquivalentName(
2607
0
                            mapping.wkt2_name, methodName.c_str())) {
2608
0
                        methodMapping = &mapping;
2609
0
                    }
2610
0
                }
2611
0
                if (methodMapping == nullptr ||
2612
0
                    methodMapping->proj_name_main == nullptr) {
2613
0
                    throw FactoryException("Cannot insert projection with "
2614
0
                                           "method without identifier");
2615
0
                }
2616
0
                methodAuthName = "PROJ";
2617
0
                methodCode = methodMapping->proj_name_main;
2618
0
                if (methodMapping->proj_name_aux) {
2619
0
                    methodCode += ' ';
2620
0
                    methodCode += methodMapping->proj_name_aux;
2621
0
                }
2622
0
            }
2623
0
        } else {
2624
0
            const auto &methodId = methodIds.front();
2625
0
            methodAuthName = *(methodId->codeSpace());
2626
0
            methodCode = methodId->code();
2627
0
        }
2628
2629
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2630
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2631
0
                                   convAuthName.c_str(), convCode.c_str(),
2632
0
                                   conversion->nameStr().c_str(),
2633
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2634
0
                                   method->nameStr().c_str());
2635
0
        const auto &srcValues = conversion->parameterValues();
2636
0
        if (srcValues.size() > N_MAX_PARAMS) {
2637
0
            throw FactoryException("Cannot insert projection with more than " +
2638
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2639
0
                                   " parameters");
2640
0
        }
2641
2642
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2643
0
        if (methodMapping == nullptr) {
2644
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2645
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2646
0
            } else {
2647
0
                methodMapping =
2648
0
                    operation::getMapping(method->nameStr().c_str());
2649
0
            }
2650
0
        }
2651
0
        if (methodMapping != nullptr) {
2652
            // Re-order projection parameters in their reference order
2653
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2654
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2655
0
                    auto opParamValue = dynamic_cast<
2656
0
                        const operation::OperationParameterValue *>(
2657
0
                        srcValues[i].get());
2658
0
                    if (!opParamValue) {
2659
0
                        throw FactoryException("Cannot insert projection with "
2660
0
                                               "non-OperationParameterValue");
2661
0
                    }
2662
0
                    if (methodMapping->params[j]->wkt2_name &&
2663
0
                        opParamValue->parameter()->nameStr() ==
2664
0
                            methodMapping->params[j]->wkt2_name) {
2665
0
                        values.emplace_back(srcValues[i]);
2666
0
                    }
2667
0
                }
2668
0
            }
2669
0
        }
2670
0
        if (values.size() != srcValues.size()) {
2671
0
            values = srcValues;
2672
0
        }
2673
2674
0
        for (const auto &genOpParamvalue : values) {
2675
0
            auto opParamValue =
2676
0
                dynamic_cast<const operation::OperationParameterValue *>(
2677
0
                    genOpParamvalue.get());
2678
0
            if (!opParamValue) {
2679
0
                throw FactoryException("Cannot insert projection with "
2680
0
                                       "non-OperationParameterValue");
2681
0
            }
2682
0
            const auto &param = opParamValue->parameter();
2683
0
            const auto &paramIds = param->identifiers();
2684
0
            std::string paramAuthName;
2685
0
            std::string paramCode;
2686
0
            if (paramIds.empty()) {
2687
0
                const int paramEPSGCode = param->getEPSGCode();
2688
0
                if (paramEPSGCode == 0) {
2689
0
                    throw FactoryException(
2690
0
                        "Cannot insert projection with method parameter "
2691
0
                        "without identifier");
2692
0
                }
2693
0
                paramAuthName = metadata::Identifier::EPSG;
2694
0
                paramCode = toString(paramEPSGCode);
2695
0
            } else {
2696
0
                const auto &paramId = paramIds.front();
2697
0
                paramAuthName = *(paramId->codeSpace());
2698
0
                paramCode = paramId->code();
2699
0
            }
2700
0
            const auto &value = opParamValue->parameterValue()->value();
2701
0
            const auto &unit = value.unit();
2702
0
            std::string uomAuthName;
2703
0
            std::string uomCode;
2704
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2705
0
                             sqlStatements);
2706
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2707
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2708
0
                                   param->nameStr().c_str(), value.value(),
2709
0
                                   uomAuthName.c_str(), uomCode.c_str());
2710
0
        }
2711
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2712
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2713
0
        }
2714
0
        sql += ",0);";
2715
0
        appendSql(sqlStatements, sql);
2716
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2717
0
                               allowedAuthorities, sqlStatements);
2718
0
    }
2719
2720
    // Find or insert coordinate system
2721
0
    const auto &coordinateSystem = crs->coordinateSystem();
2722
0
    std::string csAuthName;
2723
0
    std::string csCode;
2724
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2725
0
                     csAuthName, csCode, sqlStatements);
2726
2727
    // Insert new record in projected_crs table
2728
0
    const auto sql = formatStatement(
2729
0
        "INSERT INTO projected_crs VALUES("
2730
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2731
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2732
0
        "", // description
2733
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2734
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2735
0
    appendSql(sqlStatements, sql);
2736
2737
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2738
0
                           allowedAuthorities, sqlStatements);
2739
2740
0
    return sqlStatements;
2741
0
}
2742
2743
// ---------------------------------------------------------------------------
2744
2745
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2746
    const datum::VerticalReferenceFrameNNPtr &datum,
2747
    const std::string &authName, const std::string &code,
2748
    bool /* numericCode */,
2749
0
    const std::vector<std::string> &allowedAuthorities) {
2750
2751
0
    const auto self = NN_NO_CHECK(self_.lock());
2752
2753
0
    std::vector<std::string> sqlStatements;
2754
2755
    // Check if the object is already known under that code
2756
0
    std::string datumAuthName;
2757
0
    std::string datumCode;
2758
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2759
0
                           datumAuthName, datumCode);
2760
0
    if (datumAuthName == authName && datumCode == code) {
2761
0
        return {};
2762
0
    }
2763
2764
    // Insert new record in vertical_datum table
2765
0
    std::string publicationDate("NULL");
2766
0
    if (datum->publicationDate().has_value()) {
2767
0
        publicationDate = '\'';
2768
0
        publicationDate +=
2769
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2770
0
        publicationDate += '\'';
2771
0
    }
2772
0
    std::string frameReferenceEpoch("NULL");
2773
0
    const auto dynamicDatum =
2774
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2775
0
    if (dynamicDatum) {
2776
0
        frameReferenceEpoch =
2777
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2778
0
    }
2779
0
    const std::string anchor(*(datum->anchorDefinition()));
2780
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2781
0
    const auto sql = formatStatement(
2782
0
        "INSERT INTO vertical_datum VALUES("
2783
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2784
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2785
0
        "", // description
2786
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2787
0
        anchor.empty() ? nullptr : anchor.c_str(),
2788
0
        anchorEpoch.has_value()
2789
0
            ? anchorEpochToStr(
2790
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2791
0
                  .c_str()
2792
0
            : "NULL");
2793
0
    appendSql(sqlStatements, sql);
2794
2795
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2796
0
                           allowedAuthorities, sqlStatements);
2797
2798
0
    return sqlStatements;
2799
0
}
2800
2801
// ---------------------------------------------------------------------------
2802
2803
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2804
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2805
    const std::string &code, bool numericCode,
2806
0
    const std::vector<std::string> &allowedAuthorities) {
2807
2808
0
    const auto self = NN_NO_CHECK(self_.lock());
2809
2810
0
    std::vector<std::string> sqlStatements;
2811
2812
    // Find or insert datum/datum ensemble
2813
0
    std::string datumAuthName;
2814
0
    std::string datumCode;
2815
0
    const auto &ensemble = crs->datumEnsemble();
2816
0
    if (ensemble) {
2817
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2818
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2819
0
                               datumAuthName, datumCode);
2820
0
        if (datumAuthName.empty()) {
2821
0
            datumAuthName = authName;
2822
0
            if (numericCode) {
2823
0
                datumCode =
2824
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2825
0
            } else {
2826
0
                datumCode = "VERTICAL_DATUM_" + code;
2827
0
            }
2828
0
            sqlStatements = self->getInsertStatementsFor(
2829
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2830
0
                allowedAuthorities);
2831
0
        }
2832
0
    } else {
2833
0
        const auto &datum = crs->datum();
2834
0
        assert(datum);
2835
0
        const auto datumNN = NN_NO_CHECK(datum);
2836
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2837
0
                               datumAuthName, datumCode);
2838
0
        if (datumAuthName.empty()) {
2839
0
            datumAuthName = authName;
2840
0
            if (numericCode) {
2841
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2842
0
            } else {
2843
0
                datumCode = "VERTICAL_DATUM_" + code;
2844
0
            }
2845
0
            sqlStatements =
2846
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2847
0
                                             numericCode, allowedAuthorities);
2848
0
        }
2849
0
    }
2850
2851
    // Find or insert coordinate system
2852
0
    const auto &coordinateSystem = crs->coordinateSystem();
2853
0
    std::string csAuthName;
2854
0
    std::string csCode;
2855
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2856
0
                     csAuthName, csCode, sqlStatements);
2857
2858
    // Insert new record in vertical_crs table
2859
0
    const auto sql =
2860
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2861
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2862
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2863
0
                        "", // description
2864
0
                        csAuthName.c_str(), csCode.c_str(),
2865
0
                        datumAuthName.c_str(), datumCode.c_str());
2866
0
    appendSql(sqlStatements, sql);
2867
2868
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2869
0
                           allowedAuthorities, sqlStatements);
2870
2871
0
    return sqlStatements;
2872
0
}
2873
2874
// ---------------------------------------------------------------------------
2875
2876
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2877
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2878
    const std::string &code, bool numericCode,
2879
0
    const std::vector<std::string> &allowedAuthorities) {
2880
2881
0
    const auto self = NN_NO_CHECK(self_.lock());
2882
2883
0
    std::vector<std::string> sqlStatements;
2884
2885
0
    int counter = 1;
2886
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2887
0
    const auto &components = crs->componentReferenceSystems();
2888
0
    if (components.size() != 2) {
2889
0
        throw FactoryException(
2890
0
            "Cannot insert compound CRS with number of components != 2");
2891
0
    }
2892
2893
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2894
0
    allowedAuthoritiesTmp.emplace_back(authName);
2895
2896
0
    for (const auto &component : components) {
2897
0
        std::string compAuthName;
2898
0
        std::string compCode;
2899
2900
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2901
0
            const auto factory =
2902
0
                AuthorityFactory::create(self, allowedAuthority);
2903
0
            const auto candidates = component->identify(factory);
2904
0
            for (const auto &candidate : candidates) {
2905
0
                if (candidate.second == 100) {
2906
0
                    const auto &ids = candidate.first->identifiers();
2907
0
                    if (!ids.empty()) {
2908
0
                        const auto &id = ids.front();
2909
0
                        compAuthName = *(id->codeSpace());
2910
0
                        compCode = id->code();
2911
0
                        break;
2912
0
                    }
2913
0
                }
2914
0
                if (!compAuthName.empty()) {
2915
0
                    break;
2916
0
                }
2917
0
            }
2918
0
        }
2919
2920
0
        if (compAuthName.empty()) {
2921
0
            compAuthName = authName;
2922
0
            if (numericCode) {
2923
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2924
0
            } else {
2925
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2926
0
            }
2927
0
            const auto sqlStatementsTmp =
2928
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2929
0
                                             numericCode, allowedAuthorities);
2930
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2931
0
                                 sqlStatementsTmp.end());
2932
0
        }
2933
2934
0
        componentsId.emplace_back(
2935
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2936
2937
0
        ++counter;
2938
0
    }
2939
2940
    // Insert new record in compound_crs table
2941
0
    const auto sql = formatStatement(
2942
0
        "INSERT INTO compound_crs VALUES("
2943
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2944
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2945
0
        "", // description
2946
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2947
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2948
0
    appendSql(sqlStatements, sql);
2949
2950
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2951
0
                           allowedAuthorities, sqlStatements);
2952
2953
0
    return sqlStatements;
2954
0
}
2955
2956
//! @endcond
2957
2958
// ---------------------------------------------------------------------------
2959
2960
//! @cond Doxygen_Suppress
2961
9.83k
DatabaseContext::~DatabaseContext() {
2962
9.83k
    try {
2963
9.83k
        stopInsertStatementsSession();
2964
9.83k
    } catch (const std::exception &) {
2965
0
    }
2966
9.83k
}
2967
//! @endcond
2968
2969
// ---------------------------------------------------------------------------
2970
2971
9.83k
DatabaseContext::DatabaseContext() : d(std::make_unique<Private>()) {}
2972
2973
// ---------------------------------------------------------------------------
2974
2975
/** \brief Instantiate a database context.
2976
 *
2977
 * This database context should be used only by one thread at a time.
2978
 *
2979
 * @param databasePath Path and filename of the database. Might be empty
2980
 * string for the default rules to locate the default proj.db
2981
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2982
 * Might be empty.
2983
 * Starting with PROJ 8.1, if this parameter is an empty array,
2984
 * the PROJ_AUX_DB environment variable will be used, if set.
2985
 * It must contain one or several paths. If several paths are
2986
 * provided, they must be separated by the colon (:) character on Unix, and
2987
 * on Windows, by the semi-colon (;) character.
2988
 * @param ctx Context used for file search.
2989
 * @throw FactoryException if the database cannot be opened.
2990
 */
2991
DatabaseContextNNPtr
2992
DatabaseContext::create(const std::string &databasePath,
2993
                        const std::vector<std::string> &auxiliaryDatabasePaths,
2994
9.83k
                        PJ_CONTEXT *ctx) {
2995
9.83k
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
2996
9.83k
    auto dbCtxPrivate = dbCtx->getPrivate();
2997
9.83k
    dbCtxPrivate->open(databasePath, ctx);
2998
9.83k
    auto auxDbs(auxiliaryDatabasePaths);
2999
9.83k
    if (auxDbs.empty()) {
3000
9.83k
        const char *auxDbStr = getenv("PROJ_AUX_DB");
3001
9.83k
        if (auxDbStr) {
3002
#ifdef _WIN32
3003
            const char *delim = ";";
3004
#else
3005
0
            const char *delim = ":";
3006
0
#endif
3007
0
            auxDbs = split(auxDbStr, delim);
3008
0
        }
3009
9.83k
    }
3010
9.83k
    if (!auxDbs.empty()) {
3011
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
3012
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
3013
0
    }
3014
9.83k
    dbCtxPrivate->self_ = dbCtx.as_nullable();
3015
9.83k
    return dbCtx;
3016
9.83k
}
3017
3018
// ---------------------------------------------------------------------------
3019
3020
/** \brief Return the list of authorities used in the database.
3021
 */
3022
98
std::set<std::string> DatabaseContext::getAuthorities() const {
3023
98
    auto res = d->run("SELECT auth_name FROM authority_list");
3024
98
    std::set<std::string> list;
3025
784
    for (const auto &row : res) {
3026
784
        list.insert(row[0]);
3027
784
    }
3028
98
    return list;
3029
98
}
3030
3031
// ---------------------------------------------------------------------------
3032
3033
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
3034
 * CREATE VIEW) needed to initialize a new database.
3035
 */
3036
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
3037
0
    return d->getDatabaseStructure();
3038
0
}
3039
3040
// ---------------------------------------------------------------------------
3041
3042
/** \brief Return the path to the database.
3043
 */
3044
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
3045
3046
// ---------------------------------------------------------------------------
3047
3048
/** \brief Return a metadata item.
3049
 *
3050
 * Value remains valid while this is alive and to the next call to getMetadata
3051
 */
3052
0
const char *DatabaseContext::getMetadata(const char *key) const {
3053
0
    auto res =
3054
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
3055
0
    if (res.empty()) {
3056
0
        return nullptr;
3057
0
    }
3058
0
    d->lastMetadataValue_ = res.front()[0];
3059
0
    return d->lastMetadataValue_.c_str();
3060
0
}
3061
3062
// ---------------------------------------------------------------------------
3063
3064
/** \brief Starts a session for getInsertStatementsFor()
3065
 *
3066
 * Starts a new session for one or several calls to getInsertStatementsFor().
3067
 * An insertion session guarantees that the inserted objects will not create
3068
 * conflicting intermediate objects.
3069
 *
3070
 * The session must be stopped with stopInsertStatementsSession().
3071
 *
3072
 * Only one session may be active at a time for a given database context.
3073
 *
3074
 * @throw FactoryException in case of error.
3075
 * @since 8.1
3076
 */
3077
0
void DatabaseContext::startInsertStatementsSession() {
3078
0
    if (d->memoryDbHandle_) {
3079
0
        throw FactoryException(
3080
0
            "startInsertStatementsSession() cannot be invoked until "
3081
0
            "stopInsertStatementsSession() is.");
3082
0
    }
3083
3084
0
    d->memoryDbForInsertPath_.clear();
3085
0
    const auto sqlStatements = getDatabaseStructure();
3086
3087
    // Create a in-memory temporary sqlite3 database
3088
0
    std::ostringstream buffer;
3089
0
    buffer << "file:temp_db_for_insert_statements_";
3090
0
    buffer << this;
3091
0
    buffer << ".db?mode=memory&cache=shared";
3092
0
    d->memoryDbForInsertPath_ = buffer.str();
3093
0
    sqlite3 *memoryDbHandle = nullptr;
3094
0
    sqlite3_open_v2(
3095
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3096
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3097
0
    if (memoryDbHandle == nullptr) {
3098
0
        throw FactoryException("Cannot create in-memory database");
3099
0
    }
3100
0
    d->memoryDbHandle_ =
3101
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3102
3103
    // Fill the structure of this database
3104
0
    for (const auto &sql : sqlStatements) {
3105
0
        char *errmsg = nullptr;
3106
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3107
0
                         nullptr, &errmsg) != SQLITE_OK) {
3108
0
            const auto sErrMsg =
3109
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3110
0
            sqlite3_free(errmsg);
3111
0
            throw FactoryException(sErrMsg);
3112
0
        }
3113
0
        sqlite3_free(errmsg);
3114
0
    }
3115
3116
    // Attach this database to the current one(s)
3117
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3118
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3119
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3120
0
}
3121
3122
// ---------------------------------------------------------------------------
3123
3124
/** \brief Suggests a database code for the passed object.
3125
 *
3126
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3127
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3128
 *
3129
 * @param object Object for which to suggest a code.
3130
 * @param authName Authority name into which the object will be inserted.
3131
 * @param numericCode Whether the code should be numeric, or derived from the
3132
 * object name.
3133
 * @return the suggested code, that is guaranteed to not conflict with an
3134
 * existing one.
3135
 *
3136
 * @throw FactoryException in case of error.
3137
 * @since 8.1
3138
 */
3139
std::string
3140
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3141
                                 const std::string &authName,
3142
0
                                 bool numericCode) {
3143
0
    const char *tableName = "prime_meridian";
3144
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3145
        // tableName = "prime_meridian";
3146
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3147
0
        tableName = "ellipsoid";
3148
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3149
0
                   object.get())) {
3150
0
        tableName = "geodetic_datum";
3151
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3152
0
                   object.get())) {
3153
0
        tableName = "vertical_datum";
3154
0
    } else if (const auto ensemble =
3155
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3156
0
        const auto &datums = ensemble->datums();
3157
0
        if (!datums.empty() &&
3158
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3159
0
                datums[0].get())) {
3160
0
            tableName = "geodetic_datum";
3161
0
        } else {
3162
0
            tableName = "vertical_datum";
3163
0
        }
3164
0
    } else if (const auto boundCRS =
3165
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3166
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3167
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3168
0
        tableName = "crs_view";
3169
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3170
0
        tableName = "conversion";
3171
0
    } else {
3172
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3173
0
    }
3174
3175
0
    if (numericCode) {
3176
0
        std::string sql("SELECT MAX(code) FROM ");
3177
0
        sql += tableName;
3178
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3179
0
               "AND upper(code) = lower(code)";
3180
0
        const auto res = d->run(sql, {authName});
3181
0
        if (res.empty()) {
3182
0
            return "1";
3183
0
        }
3184
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3185
0
    }
3186
3187
0
    std::string code;
3188
0
    code.reserve(object->nameStr().size());
3189
0
    bool insertUnderscore = false;
3190
0
    for (const auto ch : toupper(object->nameStr())) {
3191
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3192
0
            if (insertUnderscore && code.back() != '_')
3193
0
                code += '_';
3194
0
            code += ch;
3195
0
            insertUnderscore = false;
3196
0
        } else {
3197
0
            insertUnderscore = true;
3198
0
        }
3199
0
    }
3200
0
    return d->findFreeCode(tableName, authName, code);
3201
0
}
3202
3203
// ---------------------------------------------------------------------------
3204
3205
/** \brief Returns SQL statements needed to insert the passed object into the
3206
 * database.
3207
 *
3208
 * startInsertStatementsSession() must have been called previously.
3209
 *
3210
 * @param object The object to insert into the database. Currently only
3211
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3212
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3213
 * @param authName Authority name into which the object will be inserted.
3214
 * @param code Code with which the object will be inserted.
3215
 * @param numericCode Whether intermediate objects that can be created should
3216
 *                    use numeric codes (true), or may be alphanumeric (false)
3217
 * @param allowedAuthorities Authorities to which intermediate objects are
3218
 *                           allowed to refer to. authName will be implicitly
3219
 *                           added to it. Note that unit, coordinate
3220
 *                           systems, projection methods and parameters will in
3221
 *                           any case be allowed to refer to EPSG.
3222
 * @throw FactoryException in case of error.
3223
 * @since 8.1
3224
 */
3225
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3226
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3227
    const std::string &code, bool numericCode,
3228
0
    const std::vector<std::string> &allowedAuthorities) {
3229
0
    if (d->memoryDbHandle_ == nullptr) {
3230
0
        throw FactoryException(
3231
0
            "startInsertStatementsSession() should be invoked first");
3232
0
    }
3233
3234
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3235
0
    if (crs) {
3236
        // Check if the object is already known under that code
3237
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3238
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3239
0
        allowedAuthoritiesTmp.emplace_back(authName);
3240
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3241
0
            const auto factory =
3242
0
                AuthorityFactory::create(self, allowedAuthority);
3243
0
            const auto candidates = crs->identify(factory);
3244
0
            for (const auto &candidate : candidates) {
3245
0
                if (candidate.second == 100) {
3246
0
                    const auto &ids = candidate.first->identifiers();
3247
0
                    for (const auto &id : ids) {
3248
0
                        if (*(id->codeSpace()) == authName &&
3249
0
                            id->code() == code) {
3250
0
                            return {};
3251
0
                        }
3252
0
                    }
3253
0
                }
3254
0
            }
3255
0
        }
3256
0
    }
3257
3258
0
    if (const auto pm =
3259
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3260
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3261
0
                                         numericCode, allowedAuthorities);
3262
0
    }
3263
3264
0
    else if (const auto ellipsoid =
3265
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3266
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3267
0
                                         numericCode, allowedAuthorities);
3268
0
    }
3269
3270
0
    else if (const auto geodeticDatum =
3271
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3272
0
                     object)) {
3273
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3274
0
                                         code, numericCode, allowedAuthorities);
3275
0
    }
3276
3277
0
    else if (const auto ensemble =
3278
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3279
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3280
0
                                         numericCode, allowedAuthorities);
3281
0
    }
3282
3283
0
    else if (const auto geodCRS =
3284
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3285
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3286
0
                                         numericCode, allowedAuthorities);
3287
0
    }
3288
3289
0
    else if (const auto projCRS =
3290
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3291
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3292
0
                                         numericCode, allowedAuthorities);
3293
0
    }
3294
3295
0
    else if (const auto verticalDatum =
3296
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3297
0
                     object)) {
3298
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3299
0
                                         code, numericCode, allowedAuthorities);
3300
0
    }
3301
3302
0
    else if (const auto vertCRS =
3303
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3304
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3305
0
                                         numericCode, allowedAuthorities);
3306
0
    }
3307
3308
0
    else if (const auto compoundCRS =
3309
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3310
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3311
0
                                         code, numericCode, allowedAuthorities);
3312
0
    }
3313
3314
0
    else if (const auto boundCRS =
3315
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3316
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3317
0
                                      numericCode, allowedAuthorities);
3318
0
    }
3319
3320
0
    else {
3321
0
        throw FactoryException(
3322
0
            "getInsertStatementsFor(): unhandled type of object");
3323
0
    }
3324
0
}
3325
3326
// ---------------------------------------------------------------------------
3327
3328
/** \brief Stops an insertion session started with
3329
 * startInsertStatementsSession()
3330
 *
3331
 * @since 8.1
3332
 */
3333
9.83k
void DatabaseContext::stopInsertStatementsSession() {
3334
9.83k
    if (d->memoryDbHandle_) {
3335
0
        d->clearCaches();
3336
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3337
0
        d->memoryDbHandle_.reset();
3338
0
        d->memoryDbForInsertPath_.clear();
3339
0
    }
3340
9.83k
}
3341
3342
// ---------------------------------------------------------------------------
3343
3344
//! @cond Doxygen_Suppress
3345
3346
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3347
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3348
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3349
0
    return ctxt;
3350
0
}
3351
3352
// ---------------------------------------------------------------------------
3353
3354
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3355
3356
// ---------------------------------------------------------------------------
3357
3358
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3359
                                             std::string &projFilename,
3360
                                             std::string &projFormat,
3361
107k
                                             bool &inverse) const {
3362
107k
    auto res = d->run(
3363
107k
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3364
107k
        "grid_alternatives WHERE original_grid_name = ? AND "
3365
107k
        "proj_grid_name <> ''",
3366
107k
        {officialName});
3367
107k
    if (res.empty()) {
3368
3.05k
        return false;
3369
3.05k
    }
3370
104k
    const auto &row = res.front();
3371
104k
    projFilename = row[0];
3372
104k
    projFormat = row[1];
3373
104k
    inverse = row[2] == "1";
3374
104k
    return true;
3375
107k
}
3376
3377
// ---------------------------------------------------------------------------
3378
3379
static std::string makeCachedGridKey(const std::string &projFilename,
3380
                                     bool networkEnabled,
3381
139k
                                     bool considerKnownGridsAsAvailable) {
3382
139k
    std::string key(projFilename);
3383
139k
    key += networkEnabled ? "true" : "false";
3384
139k
    key += considerKnownGridsAsAvailable ? "true" : "false";
3385
139k
    return key;
3386
139k
}
3387
3388
// ---------------------------------------------------------------------------
3389
3390
/** Invalidates information related to projFilename that might have been
3391
 * previously cached by lookForGridInfo().
3392
 *
3393
 * This is useful when downloading a new grid during a session.
3394
 */
3395
0
void DatabaseContext::invalidateGridInfo(const std::string &projFilename) {
3396
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false));
3397
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true));
3398
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false));
3399
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true));
3400
0
}
3401
3402
// ---------------------------------------------------------------------------
3403
3404
bool DatabaseContext::lookForGridInfo(
3405
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3406
    std::string &fullFilename, std::string &packageName, std::string &url,
3407
139k
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3408
139k
    Private::GridInfoCache info;
3409
3410
139k
    if (projFilename == "null") {
3411
        // Special case for implicit "null" grid.
3412
488
        fullFilename.clear();
3413
488
        packageName.clear();
3414
488
        url.clear();
3415
488
        directDownload = false;
3416
488
        openLicense = true;
3417
488
        gridAvailable = true;
3418
488
        return true;
3419
488
    }
3420
3421
139k
    auto ctxt = d->pjCtxt();
3422
139k
    if (ctxt == nullptr) {
3423
0
        ctxt = pj_get_default_ctx();
3424
0
        d->setPjCtxt(ctxt);
3425
0
    }
3426
3427
139k
    const std::string key(makeCachedGridKey(
3428
139k
        projFilename, proj_context_is_network_enabled(ctxt) != false,
3429
139k
        considerKnownGridsAsAvailable));
3430
139k
    if (d->getGridInfoFromCache(key, info)) {
3431
98.8k
        fullFilename = info.fullFilename;
3432
98.8k
        packageName = info.packageName;
3433
98.8k
        url = info.url;
3434
98.8k
        directDownload = info.directDownload;
3435
98.8k
        openLicense = info.openLicense;
3436
98.8k
        gridAvailable = info.gridAvailable;
3437
98.8k
        return info.found;
3438
98.8k
    }
3439
3440
40.2k
    fullFilename.clear();
3441
40.2k
    packageName.clear();
3442
40.2k
    url.clear();
3443
40.2k
    openLicense = false;
3444
40.2k
    directDownload = false;
3445
40.2k
    gridAvailable = false;
3446
3447
40.2k
    const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() {
3448
40.2k
        fullFilename.resize(2048);
3449
40.2k
        const int errno_before = proj_context_errno(ctxt);
3450
40.2k
        bool lGridAvailable = NS_PROJ::FileManager::open_resource_file(
3451
40.2k
                                  ctxt, projFilename.c_str(), &fullFilename[0],
3452
40.2k
                                  fullFilename.size() - 1) != nullptr;
3453
40.2k
        proj_context_errno_set(ctxt, errno_before);
3454
40.2k
        fullFilename.resize(strlen(fullFilename.c_str()));
3455
40.2k
        return lGridAvailable;
3456
40.2k
    };
3457
3458
40.2k
    auto res =
3459
40.2k
        d->run("SELECT "
3460
40.2k
               "grid_packages.package_name, "
3461
40.2k
               "grid_alternatives.url, "
3462
40.2k
               "grid_packages.url AS package_url, "
3463
40.2k
               "grid_alternatives.open_license, "
3464
40.2k
               "grid_packages.open_license AS package_open_license, "
3465
40.2k
               "grid_alternatives.direct_download, "
3466
40.2k
               "grid_packages.direct_download AS package_direct_download, "
3467
40.2k
               "grid_alternatives.proj_grid_name, "
3468
40.2k
               "grid_alternatives.old_proj_grid_name "
3469
40.2k
               "FROM grid_alternatives "
3470
40.2k
               "LEFT JOIN grid_packages ON "
3471
40.2k
               "grid_alternatives.package_name = grid_packages.package_name "
3472
40.2k
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3473
40.2k
               {projFilename, projFilename});
3474
40.2k
    bool ret = !res.empty();
3475
40.2k
    if (ret) {
3476
28.8k
        const auto &row = res.front();
3477
28.8k
        packageName = std::move(row[0]);
3478
28.8k
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3479
28.8k
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3480
28.8k
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3481
3482
28.8k
        const auto &proj_grid_name = row[7];
3483
28.8k
        const auto &old_proj_grid_name = row[8];
3484
28.8k
        if (proj_grid_name != old_proj_grid_name &&
3485
28.8k
            old_proj_grid_name == projFilename) {
3486
114
            std::string fullFilenameNewName;
3487
114
            fullFilenameNewName.resize(2048);
3488
114
            const int errno_before = proj_context_errno(ctxt);
3489
114
            bool gridAvailableWithNewName =
3490
114
                pj_find_file(ctxt, proj_grid_name.c_str(),
3491
114
                             &fullFilenameNewName[0],
3492
114
                             fullFilenameNewName.size() - 1,
3493
114
                             /* disable_network = */ true) != 0;
3494
114
            proj_context_errno_set(ctxt, errno_before);
3495
114
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3496
114
            if (gridAvailableWithNewName) {
3497
0
                gridAvailable = true;
3498
0
                fullFilename = std::move(fullFilenameNewName);
3499
0
            }
3500
114
        }
3501
3502
28.8k
        if (!gridAvailable && considerKnownGridsAsAvailable &&
3503
71
            (!packageName.empty() || (!url.empty() && openLicense))) {
3504
3505
            // In considerKnownGridsAsAvailable mode, try to fetch the local
3506
            // file name if it exists, but do not attempt network access.
3507
3
            const auto network_was_enabled =
3508
3
                proj_context_is_network_enabled(ctxt);
3509
3
            proj_context_set_enable_network(ctxt, false);
3510
3
            (void)resolveFullFilename();
3511
3
            proj_context_set_enable_network(ctxt, network_was_enabled);
3512
3513
3
            gridAvailable = true;
3514
3
        }
3515
3516
28.8k
        info.packageName = packageName;
3517
28.8k
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3518
28.8k
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3519
28.6k
            if (endpoint.back() != '/') {
3520
28.6k
                endpoint += '/';
3521
28.6k
            }
3522
28.6k
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3523
28.6k
        }
3524
28.8k
        info.directDownload = directDownload;
3525
28.8k
        info.openLicense = openLicense;
3526
3527
28.8k
        if (!gridAvailable) {
3528
28.8k
            gridAvailable = resolveFullFilename();
3529
28.8k
        }
3530
28.8k
    } else {
3531
11.4k
        gridAvailable = resolveFullFilename();
3532
3533
11.4k
        if (starts_with(fullFilename, "http://") ||
3534
11.4k
            starts_with(fullFilename, "https://")) {
3535
0
            url = fullFilename;
3536
0
            fullFilename.clear();
3537
0
        }
3538
11.4k
    }
3539
3540
40.2k
    info.fullFilename = fullFilename;
3541
40.2k
    info.url = url;
3542
40.2k
    info.gridAvailable = gridAvailable;
3543
40.2k
    info.found = ret;
3544
40.2k
    d->cache(key, info);
3545
40.2k
    return ret;
3546
139k
}
3547
3548
// ---------------------------------------------------------------------------
3549
3550
/** Returns the number of queries to the database since the creation of this
3551
 * instance.
3552
 */
3553
0
unsigned int DatabaseContext::getQueryCounter() const {
3554
0
    return d->queryCounter_;
3555
0
}
3556
3557
// ---------------------------------------------------------------------------
3558
3559
bool DatabaseContext::isKnownName(const std::string &name,
3560
0
                                  const std::string &tableName) const {
3561
0
    std::string sql("SELECT 1 FROM \"");
3562
0
    sql += replaceAll(tableName, "\"", "\"\"");
3563
0
    sql += "\" WHERE name = ? LIMIT 1";
3564
0
    return !d->run(sql, {name}).empty();
3565
0
}
3566
// ---------------------------------------------------------------------------
3567
3568
std::string
3569
30.0k
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3570
30.0k
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3571
30.0k
                      "old_proj_grid_name = ?",
3572
30.0k
                      {oldProjGridName});
3573
30.0k
    if (res.empty()) {
3574
29.5k
        return std::string();
3575
29.5k
    }
3576
489
    return res.front()[0];
3577
30.0k
}
3578
3579
// ---------------------------------------------------------------------------
3580
3581
29.3k
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3582
29.3k
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3583
29.3k
                      "proj_grid_name = ?",
3584
29.3k
                      {gridName});
3585
29.3k
    if (res.empty()) {
3586
515
        return std::string();
3587
515
    }
3588
28.8k
    return res.front()[0];
3589
29.3k
}
3590
3591
// ---------------------------------------------------------------------------
3592
3593
// scripts/build_db_from_esri.py adds a second alias for
3594
// names that have '[' in them. See get_old_esri_name()
3595
// in scripts/build_db_from_esri.py
3596
// So if we only have two aliases detect that situation to get the official
3597
// new name
3598
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3599
0
    std::string first = l.front();
3600
0
    std::string second = *(std::next(l.begin()));
3601
0
    if (second.find('[') != std::string::npos)
3602
0
        std::swap(first, second);
3603
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3604
0
        second) {
3605
0
        return first;
3606
0
    }
3607
0
    return std::string();
3608
0
}
3609
3610
// ---------------------------------------------------------------------------
3611
3612
/** \brief Gets the alias name from an official name.
3613
 *
3614
 * @param officialName Official name. Mandatory
3615
 * @param tableName Table name/category. Mandatory.
3616
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3617
 *                  accepted as special names to add a constraint on the "type"
3618
 *                  column of the "geodetic_crs" table.
3619
 * @param source Source of the alias. Mandatory
3620
 * @return Alias name (or empty if not found).
3621
 * @throw FactoryException in case of error.
3622
 */
3623
std::string
3624
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3625
                                          const std::string &tableName,
3626
0
                                          const std::string &source) const {
3627
0
    std::string sql("SELECT auth_name, code FROM \"");
3628
0
    const auto genuineTableName =
3629
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3630
0
            ? "geodetic_crs"
3631
0
            : tableName;
3632
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3633
0
    sql += "\" WHERE name = ?";
3634
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3635
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3636
0
    } else if (tableName == "geographic_3D_crs") {
3637
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3638
0
    }
3639
0
    sql += " ORDER BY deprecated";
3640
0
    auto res = d->run(sql, {officialName});
3641
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3642
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3643
    // NAD83(HARN), and that's definitely not desirable.
3644
0
    if (res.empty() &&
3645
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3646
0
        res = d->run(
3647
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3648
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3649
0
            {genuineTableName, officialName});
3650
0
        if (res.size() != 1) {
3651
0
            return std::string();
3652
0
        }
3653
0
    }
3654
0
    for (const auto &row : res) {
3655
0
        auto res2 =
3656
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3657
0
                   "auth_name = ? AND code = ? AND source = ?",
3658
0
                   {genuineTableName, row[0], row[1], source});
3659
0
        if (!res2.empty()) {
3660
0
            if (res2.size() == 2 && source == "ESRI") {
3661
0
                std::list<std::string> l;
3662
0
                l.emplace_back(res2.front()[0]);
3663
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3664
0
                std::string uniqueEsriAlias = getUniqueEsriAlias(l);
3665
0
                if (!uniqueEsriAlias.empty())
3666
0
                    return uniqueEsriAlias;
3667
0
            }
3668
0
            return res2.front()[0];
3669
0
        }
3670
0
    }
3671
0
    return std::string();
3672
0
}
3673
3674
// ---------------------------------------------------------------------------
3675
3676
/** \brief Gets the alias names for an object.
3677
 *
3678
 * Either authName + code or officialName must be non empty.
3679
 *
3680
 * @param authName Authority.
3681
 * @param code Code.
3682
 * @param officialName Official name.
3683
 * @param tableName Table name/category. Mandatory.
3684
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3685
 *                  accepted as special names to add a constraint on the "type"
3686
 *                  column of the "geodetic_crs" table.
3687
 * @param source Source of the alias. May be empty.
3688
 * @return Aliases
3689
 */
3690
std::list<std::string> DatabaseContext::getAliases(
3691
    const std::string &authName, const std::string &code,
3692
    const std::string &officialName, const std::string &tableName,
3693
266k
    const std::string &source) const {
3694
3695
266k
    std::list<std::string> res;
3696
266k
    const auto key(authName + code + officialName + tableName + source);
3697
266k
    if (d->cacheAliasNames_.tryGet(key, res)) {
3698
256k
        return res;
3699
256k
    }
3700
3701
9.39k
    std::string resolvedAuthName(authName);
3702
9.39k
    std::string resolvedCode(code);
3703
9.39k
    const auto genuineTableName =
3704
9.39k
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3705
9.39k
            ? "geodetic_crs"
3706
9.39k
            : tableName;
3707
9.39k
    if (authName.empty() || code.empty()) {
3708
5.45k
        std::string sql("SELECT auth_name, code FROM \"");
3709
5.45k
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3710
5.45k
        sql += "\" WHERE name = ?";
3711
5.45k
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3712
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3713
5.45k
        } else if (tableName == "geographic_3D_crs") {
3714
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3715
0
        }
3716
5.45k
        sql += " ORDER BY deprecated";
3717
5.45k
        auto resSql = d->run(sql, {officialName});
3718
5.45k
        if (resSql.empty()) {
3719
3.31k
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3720
3.31k
                            "table_name = ? AND "
3721
3.31k
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3722
3.31k
                            {genuineTableName, officialName});
3723
3.31k
            if (resSql.size() != 1) {
3724
1.71k
                d->cacheAliasNames_.insert(key, res);
3725
1.71k
                return res;
3726
1.71k
            }
3727
3.31k
        }
3728
3.74k
        const auto &row = resSql.front();
3729
3.74k
        resolvedAuthName = row[0];
3730
3.74k
        resolvedCode = row[1];
3731
3.74k
    }
3732
7.67k
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3733
7.67k
                    "auth_name = ? AND code = ?");
3734
7.67k
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3735
7.67k
    if (source == "not EPSG_OLD") {
3736
46
        sql += " AND source != 'EPSG_OLD'";
3737
7.63k
    } else if (!source.empty()) {
3738
0
        sql += " AND source = ?";
3739
0
        params.emplace_back(source);
3740
0
    }
3741
7.67k
    auto resSql = d->run(sql, params);
3742
27.8k
    for (const auto &row : resSql) {
3743
27.8k
        res.emplace_back(row[0]);
3744
27.8k
    }
3745
3746
7.67k
    if (res.size() == 2 && source == "ESRI") {
3747
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3748
0
        if (!uniqueEsriAlias.empty()) {
3749
0
            res.clear();
3750
0
            res.emplace_back(uniqueEsriAlias);
3751
0
        }
3752
0
    }
3753
3754
7.67k
    d->cacheAliasNames_.insert(key, res);
3755
7.67k
    return res;
3756
9.39k
}
3757
3758
// ---------------------------------------------------------------------------
3759
3760
/** \brief Return the 'name' column of a table for an object
3761
 *
3762
 * @param tableName Table name/category.
3763
 * @param authName Authority name of the object.
3764
 * @param code Code of the object
3765
 * @return Name (or empty)
3766
 * @throw FactoryException in case of error.
3767
 */
3768
std::string DatabaseContext::getName(const std::string &tableName,
3769
                                     const std::string &authName,
3770
125k
                                     const std::string &code) const {
3771
125k
    std::string res;
3772
125k
    const auto key(tableName + authName + code);
3773
125k
    if (d->cacheNames_.tryGet(key, res)) {
3774
122k
        return res;
3775
122k
    }
3776
3777
3.66k
    std::string sql("SELECT name FROM \"");
3778
3.66k
    sql += replaceAll(tableName, "\"", "\"\"");
3779
3.66k
    sql += "\" WHERE auth_name = ? AND code = ?";
3780
3.66k
    auto sqlRes = d->run(sql, {authName, code});
3781
3.66k
    if (sqlRes.empty()) {
3782
2
        res.clear();
3783
3.66k
    } else {
3784
3.66k
        res = sqlRes.front()[0];
3785
3.66k
    }
3786
3.66k
    d->cacheNames_.insert(key, res);
3787
3.66k
    return res;
3788
125k
}
3789
3790
// ---------------------------------------------------------------------------
3791
3792
/** \brief Return the 'text_definition' column of a table for an object
3793
 *
3794
 * @param tableName Table name/category.
3795
 * @param authName Authority name of the object.
3796
 * @param code Code of the object
3797
 * @return Text definition (or empty)
3798
 * @throw FactoryException in case of error.
3799
 */
3800
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3801
                                               const std::string &authName,
3802
0
                                               const std::string &code) const {
3803
0
    std::string sql("SELECT text_definition FROM \"");
3804
0
    sql += replaceAll(tableName, "\"", "\"\"");
3805
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3806
0
    auto res = d->run(sql, {authName, code});
3807
0
    if (res.empty()) {
3808
0
        return std::string();
3809
0
    }
3810
0
    return res.front()[0];
3811
0
}
3812
3813
// ---------------------------------------------------------------------------
3814
3815
/** \brief Return the allowed authorities when researching transformations
3816
 * between different authorities.
3817
 *
3818
 * @throw FactoryException in case of error.
3819
 */
3820
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3821
    const std::string &sourceAuthName,
3822
78.0k
    const std::string &targetAuthName) const {
3823
3824
78.0k
    const auto key(sourceAuthName + targetAuthName);
3825
78.0k
    auto hit = d->cacheAllowedAuthorities_.find(key);
3826
78.0k
    if (hit != d->cacheAllowedAuthorities_.end()) {
3827
75.9k
        return hit->second;
3828
75.9k
    }
3829
3830
2.09k
    auto sqlRes = d->run(
3831
2.09k
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3832
2.09k
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3833
2.09k
        {sourceAuthName, targetAuthName});
3834
2.09k
    if (sqlRes.empty()) {
3835
457
        sqlRes = d->run(
3836
457
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3837
457
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3838
457
            {sourceAuthName});
3839
457
    }
3840
2.09k
    if (sqlRes.empty()) {
3841
457
        sqlRes = d->run(
3842
457
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3843
457
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3844
457
            {targetAuthName});
3845
457
    }
3846
2.09k
    if (sqlRes.empty()) {
3847
302
        sqlRes = d->run(
3848
302
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3849
302
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3850
302
            {});
3851
302
    }
3852
2.09k
    if (sqlRes.empty()) {
3853
302
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3854
302
        return std::vector<std::string>();
3855
302
    }
3856
1.78k
    auto res = split(sqlRes.front()[0], ',');
3857
1.78k
    d->cacheAllowedAuthorities_[key] = res;
3858
1.78k
    return res;
3859
2.09k
}
3860
3861
// ---------------------------------------------------------------------------
3862
3863
std::list<std::pair<std::string, std::string>>
3864
DatabaseContext::getNonDeprecated(const std::string &tableName,
3865
                                  const std::string &authName,
3866
0
                                  const std::string &code) const {
3867
0
    auto sqlRes =
3868
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3869
0
               "FROM deprecation "
3870
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3871
0
               "AND deprecated_code = ?",
3872
0
               {tableName, authName, code});
3873
0
    std::list<std::pair<std::string, std::string>> res;
3874
0
    for (const auto &row : sqlRes) {
3875
0
        const auto &source = row[2];
3876
0
        if (source == "PROJ") {
3877
0
            const auto &replacement_auth_name = row[0];
3878
0
            const auto &replacement_code = row[1];
3879
0
            res.emplace_back(replacement_auth_name, replacement_code);
3880
0
        }
3881
0
    }
3882
0
    if (!res.empty()) {
3883
0
        return res;
3884
0
    }
3885
0
    for (const auto &row : sqlRes) {
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
    return res;
3891
0
}
3892
3893
// ---------------------------------------------------------------------------
3894
3895
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3896
941
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3897
941
    if (cacheAuthNameWithVersion_.empty()) {
3898
196
        const auto sqlRes =
3899
196
            run("SELECT versioned_auth_name, auth_name, version, priority "
3900
196
                "FROM versioned_auth_name_mapping");
3901
196
        for (const auto &row : sqlRes) {
3902
196
            VersionedAuthName van;
3903
196
            van.versionedAuthName = row[0];
3904
196
            van.authName = row[1];
3905
196
            van.version = row[2];
3906
196
            van.priority = atoi(row[3].c_str());
3907
196
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3908
196
        }
3909
196
    }
3910
941
    return cacheAuthNameWithVersion_;
3911
941
}
3912
3913
// ---------------------------------------------------------------------------
3914
3915
// From IAU_2015 returns (IAU,2015)
3916
bool DatabaseContext::getAuthorityAndVersion(
3917
    const std::string &versionedAuthName, std::string &authNameOut,
3918
0
    std::string &versionOut) {
3919
3920
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3921
0
        if (van.versionedAuthName == versionedAuthName) {
3922
0
            authNameOut = van.authName;
3923
0
            versionOut = van.version;
3924
0
            return true;
3925
0
        }
3926
0
    }
3927
0
    return false;
3928
0
}
3929
3930
// ---------------------------------------------------------------------------
3931
3932
// From IAU and 2015, returns IAU_2015
3933
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3934
                                            const std::string &version,
3935
834
                                            std::string &versionedAuthNameOut) {
3936
3937
834
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3938
834
        if (van.authName == authName && van.version == version) {
3939
0
            versionedAuthNameOut = van.versionedAuthName;
3940
0
            return true;
3941
0
        }
3942
834
    }
3943
834
    return false;
3944
834
}
3945
3946
// ---------------------------------------------------------------------------
3947
3948
// From IAU returns IAU_latest, ... IAU_2015
3949
std::vector<std::string>
3950
107
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3951
3952
107
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3953
107
    std::vector<VersionedAuthNamePriority> tmp;
3954
107
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3955
107
        if (van.authName == authName) {
3956
3
            tmp.emplace_back(
3957
3
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3958
3
        }
3959
107
    }
3960
107
    std::vector<std::string> res;
3961
107
    if (!tmp.empty()) {
3962
        // Sort by decreasing priority
3963
3
        std::sort(tmp.begin(), tmp.end(),
3964
3
                  [](const VersionedAuthNamePriority &a,
3965
3
                     const VersionedAuthNamePriority &b) {
3966
0
                      return b.second > a.second;
3967
0
                  });
3968
3
        for (const auto &pair : tmp)
3969
3
            res.emplace_back(pair.first);
3970
3
    }
3971
107
    return res;
3972
107
}
3973
3974
// ---------------------------------------------------------------------------
3975
3976
std::vector<operation::CoordinateOperationNNPtr>
3977
DatabaseContext::getTransformationsForGridName(
3978
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3979
0
    auto sqlRes = databaseContext->d->run(
3980
0
        "SELECT auth_name, code FROM grid_transformation "
3981
0
        "WHERE grid_name = ? OR grid_name IN "
3982
0
        "(SELECT original_grid_name FROM grid_alternatives "
3983
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3984
0
        {gridName, gridName});
3985
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3986
0
    for (const auto &row : sqlRes) {
3987
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3988
0
                             ->createCoordinateOperation(row[1], true));
3989
0
    }
3990
0
    return res;
3991
0
}
3992
3993
// ---------------------------------------------------------------------------
3994
3995
// Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame
3996
// transformation, where they neglect to reverse the sign of the rotation terms.
3997
// Cf https://github.com/OSGeo/PROJ/issues/4170 and
3998
// https://github.com/maptiler/epsg.io/issues/194
3999
// We do that only when we found a valid Coordinate Frame rotation that
4000
// has the same numeric values (and no corresponding Position Vector
4001
// transformation with same values, or Coordinate Frame transformation with
4002
// opposite sign for rotation terms, both are highly unlikely)
4003
bool DatabaseContext::toWGS84AutocorrectWrongValues(
4004
    double &tx, double &ty, double &tz, double &rx, double &ry, double &rz,
4005
2
    double &scale_difference) const {
4006
2
    if (rx == 0 && ry == 0 && rz == 0)
4007
0
        return false;
4008
    // 9606: Position Vector transformation (geog2D domain)
4009
    // 9607: Coordinate Frame rotation (geog2D domain)
4010
2
    std::string sql(
4011
2
        "SELECT DISTINCT method_code "
4012
2
        "FROM helmert_transformation_table WHERE "
4013
2
        "abs(tx - ?) <= 1e-8 * abs(tx) AND "
4014
2
        "abs(ty - ?) <= 1e-8 * abs(ty) AND "
4015
2
        "abs(tz - ?) <= 1e-8 * abs(tz) AND "
4016
2
        "abs(rx - ?) <= 1e-8 * abs(rx) AND "
4017
2
        "abs(ry - ?) <= 1e-8 * abs(ry) AND "
4018
2
        "abs(rz - ?) <= 1e-8 * abs(rz) AND "
4019
2
        "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND "
4020
2
        "method_auth_name = 'EPSG' AND "
4021
2
        "method_code IN (9606, 9607) AND "
4022
2
        "translation_uom_auth_name = 'EPSG' AND "
4023
2
        "translation_uom_code = 9001 AND " // metre
4024
2
        "rotation_uom_auth_name = 'EPSG' AND "
4025
2
        "rotation_uom_code = 9104 AND " // arc-second
4026
2
        "scale_difference_uom_auth_name = 'EPSG' AND "
4027
2
        "scale_difference_uom_code = 9202 AND " // parts per million
4028
2
        "deprecated = 0");
4029
2
    ListOfParams params;
4030
2
    params.emplace_back(tx);
4031
2
    params.emplace_back(ty);
4032
2
    params.emplace_back(tz);
4033
2
    params.emplace_back(rx);
4034
2
    params.emplace_back(ry);
4035
2
    params.emplace_back(rz);
4036
2
    params.emplace_back(scale_difference);
4037
2
    bool bFound9606 = false;
4038
2
    bool bFound9607 = false;
4039
2
    for (const auto &row : d->run(sql, params)) {
4040
0
        if (row[0] == "9606") {
4041
0
            bFound9606 = true;
4042
0
        } else if (row[0] == "9607") {
4043
0
            bFound9607 = true;
4044
0
        }
4045
0
    }
4046
2
    if (bFound9607 && !bFound9606) {
4047
0
        params.clear();
4048
0
        params.emplace_back(tx);
4049
0
        params.emplace_back(ty);
4050
0
        params.emplace_back(tz);
4051
0
        params.emplace_back(-rx);
4052
0
        params.emplace_back(-ry);
4053
0
        params.emplace_back(-rz);
4054
0
        params.emplace_back(scale_difference);
4055
0
        if (d->run(sql, params).empty()) {
4056
0
            if (d->pjCtxt()) {
4057
0
                pj_log(d->pjCtxt(), PJ_LOG_ERROR,
4058
0
                       "Auto-correcting wrong sign of rotation terms of "
4059
0
                       "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to "
4060
0
                       "%s,%s,%s,%s,%s,%s,%s",
4061
0
                       internal::toString(tx).c_str(),
4062
0
                       internal::toString(ty).c_str(),
4063
0
                       internal::toString(tz).c_str(),
4064
0
                       internal::toString(rx).c_str(),
4065
0
                       internal::toString(ry).c_str(),
4066
0
                       internal::toString(rz).c_str(),
4067
0
                       internal::toString(scale_difference).c_str(),
4068
0
                       internal::toString(tx).c_str(),
4069
0
                       internal::toString(ty).c_str(),
4070
0
                       internal::toString(tz).c_str(),
4071
0
                       internal::toString(-rx).c_str(),
4072
0
                       internal::toString(-ry).c_str(),
4073
0
                       internal::toString(-rz).c_str(),
4074
0
                       internal::toString(scale_difference).c_str());
4075
0
            }
4076
0
            rx = -rx;
4077
0
            ry = -ry;
4078
0
            rz = -rz;
4079
0
            return true;
4080
0
        }
4081
0
    }
4082
2
    return false;
4083
2
}
4084
4085
//! @endcond
4086
4087
// ---------------------------------------------------------------------------
4088
4089
//! @cond Doxygen_Suppress
4090
struct AuthorityFactory::Private {
4091
    Private(const DatabaseContextNNPtr &contextIn,
4092
            const std::string &authorityName)
4093
1.00M
        : context_(contextIn), authority_(authorityName) {}
4094
4095
2.88M
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
4096
4097
4.11M
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
4098
4.11M
        return context_;
4099
4.11M
    }
4100
4101
    // cppcheck-suppress functionStatic
4102
1.00M
    void setThis(AuthorityFactoryNNPtr factory) {
4103
1.00M
        thisFactory_ = factory.as_nullable();
4104
1.00M
    }
4105
4106
    // cppcheck-suppress functionStatic
4107
18.4k
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
4108
4109
1.03M
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
4110
1.03M
        if (auth_name == authority_) {
4111
781k
            return NN_NO_CHECK(thisFactory_.lock());
4112
781k
        }
4113
257k
        return AuthorityFactory::create(context_, auth_name);
4114
1.03M
    }
4115
4116
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
4117
                                  bool considerKnownGridsAsAvailable);
4118
4119
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
4120
                                      const std::string &code);
4121
4122
    util::PropertyMap
4123
    createProperties(const std::string &code, const std::string &name,
4124
                     bool deprecated,
4125
                     const std::vector<ObjectDomainNNPtr> &usages);
4126
4127
    util::PropertyMap
4128
    createPropertiesSearchUsages(const std::string &table_name,
4129
                                 const std::string &code,
4130
                                 const std::string &name, bool deprecated);
4131
4132
    util::PropertyMap createPropertiesSearchUsages(
4133
        const std::string &table_name, const std::string &code,
4134
        const std::string &name, bool deprecated, const std::string &remarks);
4135
4136
    SQLResultSet run(const std::string &sql,
4137
                     const ListOfParams &parameters = ListOfParams());
4138
4139
    SQLResultSet runWithCodeParam(const std::string &sql,
4140
                                  const std::string &code);
4141
4142
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
4143
4144
283k
    bool hasAuthorityRestriction() const {
4145
283k
        return !authority_.empty() && authority_ != "any";
4146
283k
    }
4147
4148
    SQLResultSet createProjectedCRSBegin(const std::string &code);
4149
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
4150
                                                 const SQLResultSet &res);
4151
4152
    SQLResultSet createDerivedProjectedCRSBegin(const std::string &code);
4153
    crs::DerivedProjectedCRSNNPtr
4154
    createDerivedProjectedCRSEnd(const std::string &code,
4155
                                 const SQLResultSet &res);
4156
4157
  private:
4158
    DatabaseContextNNPtr context_;
4159
    std::string authority_;
4160
    std::weak_ptr<AuthorityFactory> thisFactory_{};
4161
};
4162
4163
// ---------------------------------------------------------------------------
4164
4165
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
4166
909k
                                            const ListOfParams &parameters) {
4167
909k
    return context()->getPrivate()->run(sql, parameters);
4168
909k
}
4169
4170
// ---------------------------------------------------------------------------
4171
4172
SQLResultSet
4173
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
4174
416k
                                            const std::string &code) {
4175
416k
    return run(sql, {authority(), code});
4176
416k
}
4177
4178
// ---------------------------------------------------------------------------
4179
4180
SQLResultSet
4181
AuthorityFactory::Private::runWithCodeParam(const char *sql,
4182
373k
                                            const std::string &code) {
4183
373k
    return runWithCodeParam(std::string(sql), code);
4184
373k
}
4185
4186
// ---------------------------------------------------------------------------
4187
4188
UnitOfMeasure
4189
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
4190
115k
                                               const std::string &code) {
4191
115k
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
4192
115k
}
4193
4194
// ---------------------------------------------------------------------------
4195
4196
util::PropertyMap AuthorityFactory::Private::createProperties(
4197
    const std::string &code, const std::string &name, bool deprecated,
4198
283k
    const std::vector<ObjectDomainNNPtr> &usages) {
4199
283k
    auto props = util::PropertyMap()
4200
283k
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
4201
283k
                     .set(metadata::Identifier::CODE_KEY, code)
4202
283k
                     .set(common::IdentifiedObject::NAME_KEY, name);
4203
283k
    if (deprecated) {
4204
286
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
4205
286
    }
4206
283k
    if (!usages.empty()) {
4207
4208
270k
        auto array(util::ArrayOfBaseObject::create());
4209
270k
        for (const auto &usage : usages) {
4210
270k
            array->add(usage);
4211
270k
        }
4212
270k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
4213
270k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
4214
270k
    }
4215
283k
    return props;
4216
283k
}
4217
4218
// ---------------------------------------------------------------------------
4219
4220
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4221
    const std::string &table_name, const std::string &code,
4222
271k
    const std::string &name, bool deprecated) {
4223
4224
271k
    SQLResultSet res;
4225
271k
    if (table_name == "geodetic_crs" && code == "4326" &&
4226
3.04k
        authority() == "EPSG") {
4227
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
4228
        // description is super verbose.
4229
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
4230
        // To avoid churn in our WKT2 output, hot patch to the usage of
4231
        // 10.076 and earlier
4232
3.04k
        res = run("SELECT extent.description, extent.south_lat, "
4233
3.04k
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
4234
3.04k
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
4235
3.04k
                  "extent.code = 1262 and scope.code = 1183");
4236
268k
    } else {
4237
268k
        const std::string sql(
4238
268k
            "SELECT extent.description, extent.south_lat, "
4239
268k
            "extent.north_lat, extent.west_lon, extent.east_lon, "
4240
268k
            "scope.scope, "
4241
268k
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
4242
268k
            "AS score "
4243
268k
            "FROM usage "
4244
268k
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
4245
268k
            "usage.extent_code = extent.code "
4246
268k
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
4247
268k
            "usage.scope_code = scope.code "
4248
268k
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
4249
268k
            "object_code = ? AND "
4250
            // We voluntary exclude extent and scope with a specific code
4251
268k
            "NOT (usage.extent_auth_name = 'PROJ' AND "
4252
268k
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
4253
268k
            "NOT (usage.scope_auth_name = 'PROJ' AND "
4254
268k
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4255
268k
            "ORDER BY score, usage.auth_name, usage.code");
4256
268k
        res = run(sql, {table_name, authority(), code});
4257
268k
    }
4258
271k
    std::vector<ObjectDomainNNPtr> usages;
4259
271k
    for (const auto &row : res) {
4260
270k
        try {
4261
270k
            size_t idx = 0;
4262
270k
            const auto &extent_description = row[idx++];
4263
270k
            const auto &south_lat_str = row[idx++];
4264
270k
            const auto &north_lat_str = row[idx++];
4265
270k
            const auto &west_lon_str = row[idx++];
4266
270k
            const auto &east_lon_str = row[idx++];
4267
270k
            const auto &scope = row[idx];
4268
4269
270k
            util::optional<std::string> scopeOpt;
4270
270k
            if (!scope.empty()) {
4271
270k
                scopeOpt = scope;
4272
270k
            }
4273
4274
270k
            metadata::ExtentPtr extent;
4275
270k
            if (south_lat_str.empty()) {
4276
0
                extent = metadata::Extent::create(
4277
0
                             util::optional<std::string>(extent_description),
4278
0
                             {}, {}, {})
4279
0
                             .as_nullable();
4280
270k
            } else {
4281
270k
                double south_lat = c_locale_stod(south_lat_str);
4282
270k
                double north_lat = c_locale_stod(north_lat_str);
4283
270k
                double west_lon = c_locale_stod(west_lon_str);
4284
270k
                double east_lon = c_locale_stod(east_lon_str);
4285
270k
                auto bbox = metadata::GeographicBoundingBox::create(
4286
270k
                    west_lon, south_lat, east_lon, north_lat);
4287
270k
                extent = metadata::Extent::create(
4288
270k
                             util::optional<std::string>(extent_description),
4289
270k
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4290
270k
                             std::vector<metadata::VerticalExtentNNPtr>(),
4291
270k
                             std::vector<metadata::TemporalExtentNNPtr>())
4292
270k
                             .as_nullable();
4293
270k
            }
4294
4295
270k
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4296
270k
        } catch (const std::exception &) {
4297
0
        }
4298
270k
    }
4299
271k
    return createProperties(code, name, deprecated, std::move(usages));
4300
271k
}
4301
4302
// ---------------------------------------------------------------------------
4303
4304
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4305
    const std::string &table_name, const std::string &code,
4306
215k
    const std::string &name, bool deprecated, const std::string &remarks) {
4307
215k
    auto props =
4308
215k
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4309
215k
    if (!remarks.empty())
4310
181k
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4311
215k
    return props;
4312
215k
}
4313
4314
// ---------------------------------------------------------------------------
4315
4316
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4317
    const operation::CoordinateOperationNNPtr &op,
4318
64.0k
    bool considerKnownGridsAsAvailable) {
4319
4320
64.0k
    struct DisableNetwork {
4321
64.0k
        const DatabaseContextNNPtr &m_dbContext;
4322
64.0k
        bool m_old_network_enabled = false;
4323
4324
64.0k
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4325
64.0k
            : m_dbContext(l_context) {
4326
64.0k
            auto ctxt = m_dbContext->d->pjCtxt();
4327
64.0k
            if (ctxt == nullptr) {
4328
0
                ctxt = pj_get_default_ctx();
4329
0
                m_dbContext->d->setPjCtxt(ctxt);
4330
0
            }
4331
64.0k
            m_old_network_enabled =
4332
64.0k
                proj_context_is_network_enabled(ctxt) != FALSE;
4333
64.0k
            if (m_old_network_enabled)
4334
0
                proj_context_set_enable_network(ctxt, false);
4335
64.0k
        }
4336
4337
64.0k
        ~DisableNetwork() {
4338
64.0k
            if (m_old_network_enabled) {
4339
0
                auto ctxt = m_dbContext->d->pjCtxt();
4340
0
                proj_context_set_enable_network(ctxt, true);
4341
0
            }
4342
64.0k
        }
4343
64.0k
    };
4344
4345
64.0k
    auto &l_context = context();
4346
    // Temporarily disable networking as we are only interested in known grids
4347
64.0k
    DisableNetwork disabler(l_context);
4348
4349
64.0k
    for (const auto &gridDesc :
4350
64.0k
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4351
42.7k
        if (!gridDesc.available) {
4352
42.7k
            return true;
4353
42.7k
        }
4354
42.7k
    }
4355
21.2k
    return false;
4356
64.0k
}
4357
4358
//! @endcond
4359
4360
// ---------------------------------------------------------------------------
4361
4362
//! @cond Doxygen_Suppress
4363
1.00M
AuthorityFactory::~AuthorityFactory() = default;
4364
//! @endcond
4365
4366
// ---------------------------------------------------------------------------
4367
4368
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4369
                                   const std::string &authorityName)
4370
1.00M
    : d(std::make_unique<Private>(context, authorityName)) {}
4371
4372
// ---------------------------------------------------------------------------
4373
4374
// clang-format off
4375
/** \brief Instantiate a AuthorityFactory.
4376
 *
4377
 * The authority name might be set to the empty string in the particular case
4378
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4379
 * is called.
4380
 *
4381
 * @param context Context.
4382
 * @param authorityName Authority name.
4383
 * @return new AuthorityFactory.
4384
 */
4385
// clang-format on
4386
4387
AuthorityFactoryNNPtr
4388
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4389
1.00M
                         const std::string &authorityName) {
4390
1.00M
    const auto getFactory = [&context, &authorityName]() {
4391
1.00M
        for (const auto &knownName :
4392
1.53M
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4393
1.53M
            if (ci_equal(authorityName, knownName)) {
4394
802k
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4395
802k
                    context, knownName);
4396
802k
            }
4397
1.53M
        }
4398
200k
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4399
200k
            context, authorityName);
4400
1.00M
    };
4401
1.00M
    auto factory = getFactory();
4402
1.00M
    factory->d->setThis(factory);
4403
1.00M
    return factory;
4404
1.00M
}
4405
4406
// ---------------------------------------------------------------------------
4407
4408
/** \brief Returns the database context. */
4409
888k
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4410
888k
    return d->context();
4411
888k
}
4412
4413
// ---------------------------------------------------------------------------
4414
4415
//! @cond Doxygen_Suppress
4416
AuthorityFactory::CRSInfo::CRSInfo()
4417
0
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4418
0
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4419
0
      north_lat_degree{}, areaName{}, projectionMethodName{},
4420
0
      celestialBodyName{} {}
4421
//! @endcond
4422
4423
// ---------------------------------------------------------------------------
4424
4425
/** \brief Returns an arbitrary object from a code.
4426
 *
4427
 * The returned object will typically be an instance of Datum,
4428
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4429
 * the object is know at compile time, it is recommended to invoke the most
4430
 * precise method instead of this one (for example
4431
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4432
 * if the caller know he is asking for a coordinate reference system).
4433
 *
4434
 * If there are several objects with the same code, a FactoryException is
4435
 * thrown.
4436
 *
4437
 * @param code Object code allocated by authority. (e.g. "4326")
4438
 * @return object.
4439
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4440
 * @throw FactoryException in case of other errors.
4441
 */
4442
4443
util::BaseObjectNNPtr
4444
0
AuthorityFactory::createObject(const std::string &code) const {
4445
4446
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4447
0
                                   "WHERE auth_name = ? AND code = ?",
4448
0
                                   code);
4449
0
    if (res.empty()) {
4450
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4451
0
    }
4452
0
    if (res.size() != 1) {
4453
0
        std::string msg(
4454
0
            "More than one object matching specified code. Objects found in ");
4455
0
        bool first = true;
4456
0
        for (const auto &row : res) {
4457
0
            if (!first)
4458
0
                msg += ", ";
4459
0
            msg += row[0];
4460
0
            first = false;
4461
0
        }
4462
0
        throw FactoryException(msg);
4463
0
    }
4464
0
    const auto &first_row = res.front();
4465
0
    const auto &table_name = first_row[0];
4466
0
    const auto &type = first_row[1];
4467
0
    if (table_name == "extent") {
4468
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4469
0
            createExtent(code));
4470
0
    }
4471
0
    if (table_name == "unit_of_measure") {
4472
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4473
0
            createUnitOfMeasure(code));
4474
0
    }
4475
0
    if (table_name == "prime_meridian") {
4476
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4477
0
            createPrimeMeridian(code));
4478
0
    }
4479
0
    if (table_name == "ellipsoid") {
4480
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4481
0
            createEllipsoid(code));
4482
0
    }
4483
0
    if (table_name == "geodetic_datum") {
4484
0
        if (type == "ensemble") {
4485
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4486
0
                createDatumEnsemble(code, table_name));
4487
0
        }
4488
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4489
0
            createGeodeticDatum(code));
4490
0
    }
4491
0
    if (table_name == "vertical_datum") {
4492
0
        if (type == "ensemble") {
4493
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4494
0
                createDatumEnsemble(code, table_name));
4495
0
        }
4496
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4497
0
            createVerticalDatum(code));
4498
0
    }
4499
0
    if (table_name == "engineering_datum") {
4500
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4501
0
            createEngineeringDatum(code));
4502
0
    }
4503
0
    if (table_name == "geodetic_crs") {
4504
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4505
0
            createGeodeticCRS(code));
4506
0
    }
4507
0
    if (table_name == "vertical_crs") {
4508
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4509
0
            createVerticalCRS(code));
4510
0
    }
4511
0
    if (table_name == "projected_crs") {
4512
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4513
0
            createProjectedCRS(code));
4514
0
    }
4515
0
    if (table_name == "derived_projected_crs") {
4516
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4517
0
            createDerivedProjectedCRS(code));
4518
0
    }
4519
0
    if (table_name == "compound_crs") {
4520
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4521
0
            createCompoundCRS(code));
4522
0
    }
4523
0
    if (table_name == "engineering_crs") {
4524
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4525
0
            createEngineeringCRS(code));
4526
0
    }
4527
0
    if (table_name == "conversion") {
4528
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4529
0
            createConversion(code));
4530
0
    }
4531
0
    if (table_name == "helmert_transformation" ||
4532
0
        table_name == "grid_transformation" ||
4533
0
        table_name == "other_transformation" ||
4534
0
        table_name == "concatenated_operation") {
4535
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4536
0
            createCoordinateOperation(code, false));
4537
0
    }
4538
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4539
0
}
4540
4541
// ---------------------------------------------------------------------------
4542
4543
//! @cond Doxygen_Suppress
4544
static FactoryException buildFactoryException(const char *type,
4545
                                              const std::string &authName,
4546
                                              const std::string &code,
4547
0
                                              const std::exception &ex) {
4548
0
    return FactoryException(std::string("cannot build ") + type + " " +
4549
0
                            authName + ":" + code + ": " + ex.what());
4550
0
}
4551
//! @endcond
4552
4553
// ---------------------------------------------------------------------------
4554
4555
/** \brief Returns a metadata::Extent from the specified code.
4556
 *
4557
 * @param code Object code allocated by authority.
4558
 * @return object.
4559
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4560
 * @throw FactoryException in case of other errors.
4561
 */
4562
4563
metadata::ExtentNNPtr
4564
0
AuthorityFactory::createExtent(const std::string &code) const {
4565
0
    const auto cacheKey(d->authority() + code);
4566
0
    {
4567
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4568
0
        if (extent) {
4569
0
            return NN_NO_CHECK(extent);
4570
0
        }
4571
0
    }
4572
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4573
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4574
0
    auto res = d->runWithCodeParam(sql, code);
4575
0
    if (res.empty()) {
4576
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4577
0
                                           code);
4578
0
    }
4579
0
    try {
4580
0
        const auto &row = res.front();
4581
0
        const auto &description = row[0];
4582
0
        if (row[1].empty()) {
4583
0
            auto extent = metadata::Extent::create(
4584
0
                util::optional<std::string>(description), {}, {}, {});
4585
0
            d->context()->d->cache(cacheKey, extent);
4586
0
            return extent;
4587
0
        }
4588
0
        double south_lat = c_locale_stod(row[1]);
4589
0
        double north_lat = c_locale_stod(row[2]);
4590
0
        double west_lon = c_locale_stod(row[3]);
4591
0
        double east_lon = c_locale_stod(row[4]);
4592
0
        auto bbox = metadata::GeographicBoundingBox::create(
4593
0
            west_lon, south_lat, east_lon, north_lat);
4594
4595
0
        auto extent = metadata::Extent::create(
4596
0
            util::optional<std::string>(description),
4597
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4598
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4599
0
            std::vector<metadata::TemporalExtentNNPtr>());
4600
0
        d->context()->d->cache(cacheKey, extent);
4601
0
        return extent;
4602
4603
0
    } catch (const std::exception &ex) {
4604
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4605
0
    }
4606
0
}
4607
4608
// ---------------------------------------------------------------------------
4609
4610
/** \brief Returns a common::UnitOfMeasure from the specified code.
4611
 *
4612
 * @param code Object code allocated by authority.
4613
 * @return object.
4614
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4615
 * @throw FactoryException in case of other errors.
4616
 */
4617
4618
UnitOfMeasureNNPtr
4619
115k
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4620
115k
    const auto cacheKey(d->authority() + code);
4621
115k
    {
4622
115k
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4623
115k
        if (uom) {
4624
99.7k
            return NN_NO_CHECK(uom);
4625
99.7k
        }
4626
115k
    }
4627
15.5k
    auto res = d->context()->d->run(
4628
15.5k
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4629
15.5k
        "auth_name = ? AND code = ?",
4630
15.5k
        {d->authority(), code}, true);
4631
15.5k
    if (res.empty()) {
4632
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4633
0
                                           d->authority(), code);
4634
0
    }
4635
15.5k
    try {
4636
15.5k
        const auto &row = res.front();
4637
15.5k
        const auto &name =
4638
15.5k
            (row[0] == "degree (supplier to define representation)")
4639
15.5k
                ? UnitOfMeasure::DEGREE.name()
4640
15.5k
                : row[0];
4641
15.5k
        double conv_factor = (code == "9107" || code == "9108")
4642
15.5k
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4643
15.5k
                                 : c_locale_stod(row[1]);
4644
15.5k
        constexpr double EPS = 1e-10;
4645
15.5k
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4646
15.5k
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4647
7.36k
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4648
7.36k
        }
4649
15.5k
        if (std::fabs(conv_factor -
4650
15.5k
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4651
15.5k
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4652
1.00k
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4653
1.00k
        }
4654
15.5k
        const auto &type_str = row[2];
4655
15.5k
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4656
15.5k
        if (type_str == "length")
4657
4.29k
            unitType = UnitOfMeasure::Type::LINEAR;
4658
11.2k
        else if (type_str == "angle")
4659
9.24k
            unitType = UnitOfMeasure::Type::ANGULAR;
4660
2.05k
        else if (type_str == "scale")
4661
1.91k
            unitType = UnitOfMeasure::Type::SCALE;
4662
139
        else if (type_str == "time")
4663
139
            unitType = UnitOfMeasure::Type::TIME;
4664
15.5k
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4665
15.5k
            name, conv_factor, unitType, d->authority(), code);
4666
15.5k
        d->context()->d->cache(cacheKey, uom);
4667
15.5k
        return uom;
4668
15.5k
    } catch (const std::exception &ex) {
4669
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4670
0
                                    ex);
4671
0
    }
4672
15.5k
}
4673
4674
// ---------------------------------------------------------------------------
4675
4676
//! @cond Doxygen_Suppress
4677
static double normalizeMeasure(const std::string &uom_code,
4678
                               const std::string &value,
4679
23.9k
                               std::string &normalized_uom_code) {
4680
23.9k
    if (uom_code == "9110") // DDD.MMSSsss.....
4681
3.21k
    {
4682
3.21k
        double normalized_value = c_locale_stod(value);
4683
3.21k
        std::ostringstream buffer;
4684
3.21k
        buffer.imbue(std::locale::classic());
4685
3.21k
        constexpr size_t precision = 12;
4686
3.21k
        buffer << std::fixed << std::setprecision(precision)
4687
3.21k
               << normalized_value;
4688
3.21k
        auto formatted = buffer.str();
4689
3.21k
        size_t dotPos = formatted.find('.');
4690
3.21k
        assert(dotPos + 1 + precision == formatted.size());
4691
3.21k
        auto minutes = formatted.substr(dotPos + 1, 2);
4692
3.21k
        auto seconds = formatted.substr(dotPos + 3);
4693
3.21k
        assert(seconds.size() == precision - 2);
4694
3.21k
        normalized_value =
4695
3.21k
            (normalized_value < 0 ? -1.0 : 1.0) *
4696
3.21k
            (std::floor(std::fabs(normalized_value)) +
4697
3.21k
             c_locale_stod(minutes) / 60. +
4698
3.21k
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4699
3.21k
                 3600.);
4700
3.21k
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4701
        /* coverity[overflow_sink] */
4702
3.21k
        return normalized_value;
4703
20.7k
    } else {
4704
20.7k
        normalized_uom_code = uom_code;
4705
20.7k
        return c_locale_stod(value);
4706
20.7k
    }
4707
23.9k
}
4708
//! @endcond
4709
4710
// ---------------------------------------------------------------------------
4711
4712
/** \brief Returns a datum::PrimeMeridian from the specified code.
4713
 *
4714
 * @param code Object code allocated by authority.
4715
 * @return object.
4716
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4717
 * @throw FactoryException in case of other errors.
4718
 */
4719
4720
datum::PrimeMeridianNNPtr
4721
43.1k
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4722
43.1k
    const auto cacheKey(d->authority() + code);
4723
43.1k
    {
4724
43.1k
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4725
43.1k
        if (pm) {
4726
39.0k
            return NN_NO_CHECK(pm);
4727
39.0k
        }
4728
43.1k
    }
4729
4.12k
    auto res = d->runWithCodeParam(
4730
4.12k
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4731
4.12k
        "prime_meridian WHERE "
4732
4.12k
        "auth_name = ? AND code = ?",
4733
4.12k
        code);
4734
4.12k
    if (res.empty()) {
4735
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4736
0
                                           d->authority(), code);
4737
0
    }
4738
4.12k
    try {
4739
4.12k
        const auto &row = res.front();
4740
4.12k
        const auto &name = row[0];
4741
4.12k
        const auto &longitude = row[1];
4742
4.12k
        const auto &uom_auth_name = row[2];
4743
4.12k
        const auto &uom_code = row[3];
4744
4.12k
        const bool deprecated = row[4] == "1";
4745
4746
4.12k
        std::string normalized_uom_code(uom_code);
4747
4.12k
        const double normalized_value =
4748
4.12k
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4749
4750
4.12k
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4751
4.12k
        auto props = d->createProperties(code, name, deprecated, {});
4752
4.12k
        auto pm = datum::PrimeMeridian::create(
4753
4.12k
            props, common::Angle(normalized_value, uom));
4754
4.12k
        d->context()->d->cache(cacheKey, pm);
4755
4.12k
        return pm;
4756
4.12k
    } catch (const std::exception &ex) {
4757
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4758
0
    }
4759
4.12k
}
4760
4761
// ---------------------------------------------------------------------------
4762
4763
/** \brief Identify a celestial body from an approximate radius.
4764
 *
4765
 * @param semi_major_axis Approximate semi-major axis.
4766
 * @param tolerance Relative error allowed.
4767
 * @return celestial body name if one single match found.
4768
 * @throw FactoryException in case of error.
4769
 */
4770
4771
std::string
4772
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4773
1.59k
                                                double tolerance) const {
4774
1.59k
    auto res =
4775
1.59k
        d->run("SELECT DISTINCT name, "
4776
1.59k
               "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error "
4777
1.59k
               "FROM celestial_body WHERE rel_error <= ? "
4778
1.59k
               "ORDER BY rel_error, name",
4779
1.59k
               {semi_major_axis, tolerance});
4780
1.59k
    if (res.empty()) {
4781
1.49k
        throw FactoryException("no match found");
4782
1.49k
    }
4783
102
    constexpr int IDX_NAME = 0;
4784
102
    if (res.size() > 1) {
4785
12
        constexpr int IDX_REL_ERROR = 1;
4786
        // If the first object has a relative error of 0 and the next one
4787
        // a non-zero error, then use the first one.
4788
12
        if (res.front()[IDX_REL_ERROR] == "0" &&
4789
0
            (*std::next(res.begin()))[IDX_REL_ERROR] != "0") {
4790
0
            return res.front()[IDX_NAME];
4791
0
        }
4792
24
        for (const auto &row : res) {
4793
24
            if (row[IDX_NAME] != res.front()[IDX_NAME]) {
4794
10
                throw FactoryException("more than one match found");
4795
10
            }
4796
24
        }
4797
12
    }
4798
92
    return res.front()[IDX_NAME];
4799
102
}
4800
4801
// ---------------------------------------------------------------------------
4802
4803
/** \brief Returns a datum::Ellipsoid from the specified code.
4804
 *
4805
 * @param code Object code allocated by authority.
4806
 * @return object.
4807
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4808
 * @throw FactoryException in case of other errors.
4809
 */
4810
4811
datum::EllipsoidNNPtr
4812
43.3k
AuthorityFactory::createEllipsoid(const std::string &code) const {
4813
43.3k
    const auto cacheKey(d->authority() + code);
4814
43.3k
    {
4815
43.3k
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4816
43.3k
        if (ellps) {
4817
35.9k
            return NN_NO_CHECK(ellps);
4818
35.9k
        }
4819
43.3k
    }
4820
7.39k
    auto res = d->runWithCodeParam(
4821
7.39k
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4822
7.39k
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4823
7.39k
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4824
7.39k
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4825
7.39k
        "ellipsoid JOIN celestial_body "
4826
7.39k
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4827
7.39k
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4828
7.39k
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4829
7.39k
        code);
4830
7.39k
    if (res.empty()) {
4831
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4832
0
                                           d->authority(), code);
4833
0
    }
4834
7.39k
    try {
4835
7.39k
        const auto &row = res.front();
4836
7.39k
        const auto &name = row[0];
4837
7.39k
        const auto &semi_major_axis_str = row[1];
4838
7.39k
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4839
7.39k
        const auto &uom_auth_name = row[2];
4840
7.39k
        const auto &uom_code = row[3];
4841
7.39k
        const auto &inv_flattening_str = row[4];
4842
7.39k
        const auto &semi_minor_axis_str = row[5];
4843
7.39k
        const auto &body = row[6];
4844
7.39k
        const bool deprecated = row[7] == "1";
4845
7.39k
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4846
7.39k
        auto props = d->createProperties(code, name, deprecated, {});
4847
7.39k
        if (!inv_flattening_str.empty()) {
4848
6.45k
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4849
6.45k
                props, common::Length(semi_major_axis, uom),
4850
6.45k
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4851
6.45k
            d->context()->d->cache(cacheKey, ellps);
4852
6.45k
            return ellps;
4853
6.45k
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4854
65
            auto ellps = datum::Ellipsoid::createSphere(
4855
65
                props, common::Length(semi_major_axis, uom), body);
4856
65
            d->context()->d->cache(cacheKey, ellps);
4857
65
            return ellps;
4858
878
        } else {
4859
878
            auto ellps = datum::Ellipsoid::createTwoAxis(
4860
878
                props, common::Length(semi_major_axis, uom),
4861
878
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4862
878
            d->context()->d->cache(cacheKey, ellps);
4863
878
            return ellps;
4864
878
        }
4865
7.39k
    } catch (const std::exception &ex) {
4866
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4867
0
    }
4868
7.39k
}
4869
4870
// ---------------------------------------------------------------------------
4871
4872
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4873
 *
4874
 * @param code Object code allocated by authority.
4875
 * @return object.
4876
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4877
 * @throw FactoryException in case of other errors.
4878
 */
4879
4880
datum::GeodeticReferenceFrameNNPtr
4881
334k
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4882
4883
334k
    datum::GeodeticReferenceFramePtr datum;
4884
334k
    datum::DatumEnsemblePtr datumEnsemble;
4885
334k
    constexpr bool turnEnsembleAsDatum = true;
4886
334k
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4887
334k
                                  turnEnsembleAsDatum);
4888
334k
    return NN_NO_CHECK(datum);
4889
334k
}
4890
4891
// ---------------------------------------------------------------------------
4892
4893
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4894
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4895
377k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4896
377k
    const auto cacheKey(d->authority() + code);
4897
377k
    {
4898
377k
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4899
377k
        if (outDatumEnsemble) {
4900
325k
            if (!turnEnsembleAsDatum)
4901
23.8k
                return;
4902
301k
            outDatumEnsemble = nullptr;
4903
301k
        }
4904
353k
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4905
353k
        if (outDatum) {
4906
306k
            return;
4907
306k
        }
4908
353k
    }
4909
46.4k
    auto res = d->runWithCodeParam(
4910
46.4k
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4911
46.4k
        "prime_meridian_auth_name, prime_meridian_code, "
4912
46.4k
        "publication_date, frame_reference_epoch, "
4913
46.4k
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4914
46.4k
        "FROM geodetic_datum "
4915
46.4k
        "WHERE "
4916
46.4k
        "auth_name = ? AND code = ?",
4917
46.4k
        code);
4918
46.4k
    if (res.empty()) {
4919
22
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4920
22
                                           d->authority(), code);
4921
22
    }
4922
46.3k
    try {
4923
46.3k
        const auto &row = res.front();
4924
46.3k
        const auto &name = row[0];
4925
46.3k
        const auto &ellipsoid_auth_name = row[1];
4926
46.3k
        const auto &ellipsoid_code = row[2];
4927
46.3k
        const auto &prime_meridian_auth_name = row[3];
4928
46.3k
        const auto &prime_meridian_code = row[4];
4929
46.3k
        const auto &publication_date = row[5];
4930
46.3k
        const auto &frame_reference_epoch = row[6];
4931
46.3k
        const auto &ensemble_accuracy = row[7];
4932
46.3k
        const auto &anchor = row[8];
4933
46.3k
        const auto &anchor_epoch = row[9];
4934
46.3k
        const bool deprecated = row[10] == "1";
4935
4936
46.3k
        std::string massagedName;
4937
46.3k
        if (turnEnsembleAsDatum) {
4938
35.7k
            massagedName =
4939
35.7k
                datum::DatumEnsemble::ensembleNameToNonEnsembleName(name);
4940
35.7k
        }
4941
46.3k
        if (massagedName.empty()) {
4942
43.5k
            massagedName = name;
4943
43.5k
        }
4944
46.3k
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4945
46.3k
                                                     massagedName, deprecated);
4946
4947
46.3k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4948
3.25k
            auto resMembers =
4949
3.25k
                d->run("SELECT member_auth_name, member_code FROM "
4950
3.25k
                       "geodetic_datum_ensemble_member WHERE "
4951
3.25k
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4952
3.25k
                       "ORDER BY sequence",
4953
3.25k
                       {d->authority(), code});
4954
4955
3.25k
            std::vector<datum::DatumNNPtr> members;
4956
32.9k
            for (const auto &memberRow : resMembers) {
4957
32.9k
                members.push_back(
4958
32.9k
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4959
32.9k
            }
4960
3.25k
            auto datumEnsemble = datum::DatumEnsemble::create(
4961
3.25k
                props, std::move(members),
4962
3.25k
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4963
3.25k
            d->context()->d->cache(cacheKey, datumEnsemble);
4964
3.25k
            outDatumEnsemble = datumEnsemble.as_nullable();
4965
43.1k
        } else {
4966
43.1k
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4967
43.1k
                                 ->createEllipsoid(ellipsoid_code);
4968
43.1k
            auto pm = d->createFactory(prime_meridian_auth_name)
4969
43.1k
                          ->createPrimeMeridian(prime_meridian_code);
4970
4971
43.1k
            auto anchorOpt = util::optional<std::string>();
4972
43.1k
            if (!anchor.empty())
4973
52
                anchorOpt = anchor;
4974
43.1k
            if (!publication_date.empty()) {
4975
36.7k
                props.set("PUBLICATION_DATE", publication_date);
4976
36.7k
            }
4977
43.1k
            if (!anchor_epoch.empty()) {
4978
7.04k
                props.set("ANCHOR_EPOCH", anchor_epoch);
4979
7.04k
            }
4980
43.1k
            auto datum = frame_reference_epoch.empty()
4981
43.1k
                             ? datum::GeodeticReferenceFrame::create(
4982
15.9k
                                   props, ellipsoid, anchorOpt, pm)
4983
43.1k
                             : util::nn_static_pointer_cast<
4984
27.1k
                                   datum::GeodeticReferenceFrame>(
4985
27.1k
                                   datum::DynamicGeodeticReferenceFrame::create(
4986
27.1k
                                       props, ellipsoid, anchorOpt, pm,
4987
27.1k
                                       common::Measure(
4988
27.1k
                                           c_locale_stod(frame_reference_epoch),
4989
27.1k
                                           common::UnitOfMeasure::YEAR),
4990
27.1k
                                       util::optional<std::string>()));
4991
43.1k
            d->context()->d->cache(cacheKey, datum);
4992
43.1k
            outDatum = datum.as_nullable();
4993
43.1k
        }
4994
46.3k
    } catch (const std::exception &ex) {
4995
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
4996
0
                                    code, ex);
4997
0
    }
4998
46.3k
}
4999
5000
// ---------------------------------------------------------------------------
5001
5002
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
5003
 *
5004
 * @param code Object code allocated by authority.
5005
 * @return object.
5006
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5007
 * @throw FactoryException in case of other errors.
5008
 */
5009
5010
datum::VerticalReferenceFrameNNPtr
5011
102
AuthorityFactory::createVerticalDatum(const std::string &code) const {
5012
102
    datum::VerticalReferenceFramePtr datum;
5013
102
    datum::DatumEnsemblePtr datumEnsemble;
5014
102
    constexpr bool turnEnsembleAsDatum = true;
5015
102
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
5016
102
                                  turnEnsembleAsDatum);
5017
102
    return NN_NO_CHECK(datum);
5018
102
}
5019
5020
// ---------------------------------------------------------------------------
5021
5022
void AuthorityFactory::createVerticalDatumOrEnsemble(
5023
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
5024
1.39k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
5025
1.39k
    auto res =
5026
1.39k
        d->runWithCodeParam("SELECT name, publication_date, "
5027
1.39k
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
5028
1.39k
                            "anchor_epoch, deprecated FROM "
5029
1.39k
                            "vertical_datum WHERE auth_name = ? AND code = ?",
5030
1.39k
                            code);
5031
1.39k
    if (res.empty()) {
5032
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
5033
0
                                           d->authority(), code);
5034
0
    }
5035
1.39k
    try {
5036
1.39k
        const auto &row = res.front();
5037
1.39k
        const auto &name = row[0];
5038
1.39k
        const auto &publication_date = row[1];
5039
1.39k
        const auto &frame_reference_epoch = row[2];
5040
1.39k
        const auto &ensemble_accuracy = row[3];
5041
1.39k
        const auto &anchor = row[4];
5042
1.39k
        const auto &anchor_epoch = row[5];
5043
1.39k
        const bool deprecated = row[6] == "1";
5044
1.39k
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
5045
1.39k
                                                     name, deprecated);
5046
1.39k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
5047
24
            auto resMembers =
5048
24
                d->run("SELECT member_auth_name, member_code FROM "
5049
24
                       "vertical_datum_ensemble_member WHERE "
5050
24
                       "ensemble_auth_name = ? AND ensemble_code = ? "
5051
24
                       "ORDER BY sequence",
5052
24
                       {d->authority(), code});
5053
5054
24
            std::vector<datum::DatumNNPtr> members;
5055
102
            for (const auto &memberRow : resMembers) {
5056
102
                members.push_back(
5057
102
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5058
102
            }
5059
24
            auto datumEnsemble = datum::DatumEnsemble::create(
5060
24
                props, std::move(members),
5061
24
                metadata::PositionalAccuracy::create(ensemble_accuracy));
5062
24
            outDatumEnsemble = datumEnsemble.as_nullable();
5063
1.36k
        } else {
5064
1.36k
            if (!publication_date.empty()) {
5065
541
                props.set("PUBLICATION_DATE", publication_date);
5066
541
            }
5067
1.36k
            if (!anchor_epoch.empty()) {
5068
16
                props.set("ANCHOR_EPOCH", anchor_epoch);
5069
16
            }
5070
1.36k
            if (d->authority() == "ESRI" &&
5071
632
                starts_with(code, "from_geogdatum_")) {
5072
616
                props.set("VERT_DATUM_TYPE", "2002");
5073
616
            }
5074
1.36k
            auto anchorOpt = util::optional<std::string>();
5075
1.36k
            if (!anchor.empty())
5076
0
                anchorOpt = anchor;
5077
1.36k
            if (frame_reference_epoch.empty()) {
5078
1.36k
                outDatum =
5079
1.36k
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
5080
1.36k
                        .as_nullable();
5081
1.36k
            } else {
5082
4
                outDatum =
5083
4
                    datum::DynamicVerticalReferenceFrame::create(
5084
4
                        props, anchorOpt,
5085
4
                        util::optional<datum::RealizationMethod>(),
5086
4
                        common::Measure(c_locale_stod(frame_reference_epoch),
5087
4
                                        common::UnitOfMeasure::YEAR),
5088
4
                        util::optional<std::string>())
5089
4
                        .as_nullable();
5090
4
            }
5091
1.36k
        }
5092
1.39k
    } catch (const std::exception &ex) {
5093
0
        throw buildFactoryException("vertical reference frame", d->authority(),
5094
0
                                    code, ex);
5095
0
    }
5096
1.39k
}
5097
5098
// ---------------------------------------------------------------------------
5099
5100
/** \brief Returns a datum::EngineeringDatum from the specified code.
5101
 *
5102
 * @param code Object code allocated by authority.
5103
 * @return object.
5104
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5105
 * @throw FactoryException in case of other errors.
5106
 * @since 9.6
5107
 */
5108
5109
datum::EngineeringDatumNNPtr
5110
29
AuthorityFactory::createEngineeringDatum(const std::string &code) const {
5111
29
    auto res = d->runWithCodeParam(
5112
29
        "SELECT name, publication_date, "
5113
29
        "anchor, anchor_epoch, deprecated FROM "
5114
29
        "engineering_datum WHERE auth_name = ? AND code = ?",
5115
29
        code);
5116
29
    if (res.empty()) {
5117
0
        throw NoSuchAuthorityCodeException("engineering datum not found",
5118
0
                                           d->authority(), code);
5119
0
    }
5120
29
    try {
5121
29
        const auto &row = res.front();
5122
29
        const auto &name = row[0];
5123
29
        const auto &publication_date = row[1];
5124
29
        const auto &anchor = row[2];
5125
29
        const auto &anchor_epoch = row[3];
5126
29
        const bool deprecated = row[4] == "1";
5127
29
        auto props = d->createPropertiesSearchUsages("engineering_datum", code,
5128
29
                                                     name, deprecated);
5129
5130
29
        if (!publication_date.empty()) {
5131
4
            props.set("PUBLICATION_DATE", publication_date);
5132
4
        }
5133
29
        if (!anchor_epoch.empty()) {
5134
0
            props.set("ANCHOR_EPOCH", anchor_epoch);
5135
0
        }
5136
29
        auto anchorOpt = util::optional<std::string>();
5137
29
        if (!anchor.empty())
5138
0
            anchorOpt = anchor;
5139
29
        return datum::EngineeringDatum::create(props, anchorOpt);
5140
29
    } catch (const std::exception &ex) {
5141
0
        throw buildFactoryException("engineering datum", d->authority(), code,
5142
0
                                    ex);
5143
0
    }
5144
29
}
5145
5146
// ---------------------------------------------------------------------------
5147
5148
/** \brief Returns a datum::DatumEnsemble from the specified code.
5149
 *
5150
 * @param code Object code allocated by authority.
5151
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
5152
 * @return object.
5153
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5154
 * @throw FactoryException in case of other errors.
5155
 */
5156
5157
datum::DatumEnsembleNNPtr
5158
AuthorityFactory::createDatumEnsemble(const std::string &code,
5159
0
                                      const std::string &type) const {
5160
0
    auto res = d->run(
5161
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
5162
0
        "geodetic_datum WHERE "
5163
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
5164
0
        "UNION ALL "
5165
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
5166
0
        "vertical_datum WHERE "
5167
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
5168
0
        {d->authority(), code, d->authority(), code});
5169
0
    if (res.empty()) {
5170
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
5171
0
                                           d->authority(), code);
5172
0
    }
5173
0
    for (const auto &row : res) {
5174
0
        const std::string &gotType = row[0];
5175
0
        const std::string &name = row[1];
5176
0
        const std::string &ensembleAccuracy = row[2];
5177
0
        const bool deprecated = row[3] == "1";
5178
0
        if (type.empty() || type == gotType) {
5179
0
            auto resMembers =
5180
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
5181
0
                           "_ensemble_member WHERE "
5182
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
5183
0
                           "ORDER BY sequence",
5184
0
                       {d->authority(), code});
5185
5186
0
            std::vector<datum::DatumNNPtr> members;
5187
0
            for (const auto &memberRow : resMembers) {
5188
0
                members.push_back(
5189
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5190
0
            }
5191
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
5192
0
                                                         deprecated);
5193
0
            return datum::DatumEnsemble::create(
5194
0
                props, std::move(members),
5195
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
5196
0
        }
5197
0
    }
5198
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
5199
0
                                       d->authority(), code);
5200
0
}
5201
5202
// ---------------------------------------------------------------------------
5203
5204
/** \brief Returns a datum::Datum from the specified code.
5205
 *
5206
 * @param code Object code allocated by authority.
5207
 * @return object.
5208
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5209
 * @throw FactoryException in case of other errors.
5210
 */
5211
5212
33.0k
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
5213
33.0k
    auto res = d->run(
5214
33.0k
        "SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
5215
33.0k
        "auth_name = ? AND code = ? "
5216
33.0k
        "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
5217
33.0k
        "auth_name = ? AND code = ? "
5218
33.0k
        "UNION ALL SELECT 'engineering_datum' FROM engineering_datum "
5219
33.0k
        "WHERE "
5220
33.0k
        "auth_name = ? AND code = ?",
5221
33.0k
        {d->authority(), code, d->authority(), code, d->authority(), code});
5222
33.0k
    if (res.empty()) {
5223
0
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
5224
0
                                           code);
5225
0
    }
5226
33.0k
    const auto &type = res.front()[0];
5227
33.0k
    if (type == "geodetic_datum") {
5228
32.9k
        return createGeodeticDatum(code);
5229
32.9k
    }
5230
102
    if (type == "vertical_datum") {
5231
102
        return createVerticalDatum(code);
5232
102
    }
5233
0
    return createEngineeringDatum(code);
5234
102
}
5235
5236
// ---------------------------------------------------------------------------
5237
5238
//! @cond Doxygen_Suppress
5239
78
static cs::MeridianPtr createMeridian(const std::string &val) {
5240
78
    try {
5241
78
        const std::string degW(std::string("\xC2\xB0") + "W");
5242
78
        if (ends_with(val, degW)) {
5243
15
            return cs::Meridian::create(common::Angle(
5244
15
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
5245
15
        }
5246
63
        const std::string degE(std::string("\xC2\xB0") + "E");
5247
63
        if (ends_with(val, degE)) {
5248
63
            return cs::Meridian::create(common::Angle(
5249
63
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
5250
63
        }
5251
63
    } catch (const std::exception &) {
5252
0
    }
5253
0
    return nullptr;
5254
78
}
5255
//! @endcond
5256
5257
// ---------------------------------------------------------------------------
5258
5259
/** \brief Returns a cs::CoordinateSystem from the specified code.
5260
 *
5261
 * @param code Object code allocated by authority.
5262
 * @return object.
5263
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5264
 * @throw FactoryException in case of other errors.
5265
 */
5266
5267
cs::CoordinateSystemNNPtr
5268
46.7k
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
5269
46.7k
    const auto cacheKey(d->authority() + code);
5270
46.7k
    {
5271
46.7k
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
5272
46.7k
        if (cs) {
5273
30.5k
            return NN_NO_CHECK(cs);
5274
30.5k
        }
5275
46.7k
    }
5276
16.2k
    auto res = d->runWithCodeParam(
5277
16.2k
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
5278
16.2k
        "cs.type FROM "
5279
16.2k
        "axis LEFT JOIN coordinate_system cs ON "
5280
16.2k
        "axis.coordinate_system_auth_name = cs.auth_name AND "
5281
16.2k
        "axis.coordinate_system_code = cs.code WHERE "
5282
16.2k
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
5283
16.2k
        "BY coordinate_system_order",
5284
16.2k
        code);
5285
16.2k
    if (res.empty()) {
5286
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
5287
0
                                           d->authority(), code);
5288
0
    }
5289
5290
16.2k
    const auto &csType = res.front()[5];
5291
16.2k
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
5292
39.9k
    for (const auto &row : res) {
5293
39.9k
        const auto &name = row[0];
5294
39.9k
        const auto &abbrev = row[1];
5295
39.9k
        const auto &orientation = row[2];
5296
39.9k
        const auto &uom_auth_name = row[3];
5297
39.9k
        const auto &uom_code = row[4];
5298
39.9k
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
5299
0
            throw FactoryException("no unit of measure for an axis is only "
5300
0
                                   "supported for ordinatal CS");
5301
0
        }
5302
39.9k
        auto uom = uom_auth_name.empty()
5303
39.9k
                       ? common::UnitOfMeasure::NONE
5304
39.9k
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
5305
39.9k
        auto props =
5306
39.9k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
5307
39.9k
        const cs::AxisDirection *direction =
5308
39.9k
            cs::AxisDirection::valueOf(orientation);
5309
39.9k
        cs::MeridianPtr meridian;
5310
39.9k
        if (direction == nullptr) {
5311
78
            if (orientation == "Geocentre > equator/0"
5312
78
                               "\xC2\xB0"
5313
78
                               "E") {
5314
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
5315
78
            } else if (orientation == "Geocentre > equator/90"
5316
78
                                      "\xC2\xB0"
5317
78
                                      "E") {
5318
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
5319
78
            } else if (orientation == "Geocentre > north pole") {
5320
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
5321
78
            } else if (starts_with(orientation, "North along ")) {
5322
48
                direction = &(cs::AxisDirection::NORTH);
5323
48
                meridian =
5324
48
                    createMeridian(orientation.substr(strlen("North along ")));
5325
48
            } else if (starts_with(orientation, "South along ")) {
5326
30
                direction = &(cs::AxisDirection::SOUTH);
5327
30
                meridian =
5328
30
                    createMeridian(orientation.substr(strlen("South along ")));
5329
30
            } else {
5330
0
                throw FactoryException("unknown axis direction: " +
5331
0
                                       orientation);
5332
0
            }
5333
78
        }
5334
39.9k
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5335
39.9k
            props, abbrev, *direction, uom, meridian));
5336
39.9k
    }
5337
5338
16.2k
    const auto cacheAndRet = [this,
5339
16.2k
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5340
16.2k
        d->context()->d->cache(cacheKey, cs);
5341
16.2k
        return cs;
5342
16.2k
    };
5343
5344
16.2k
    auto props = util::PropertyMap()
5345
16.2k
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5346
16.2k
                     .set(metadata::Identifier::CODE_KEY, code);
5347
16.2k
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5348
11.4k
        if (axisList.size() == 2) {
5349
6.32k
            return cacheAndRet(
5350
6.32k
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5351
6.32k
        }
5352
5.15k
        if (axisList.size() == 3) {
5353
5.15k
            return cacheAndRet(cs::EllipsoidalCS::create(
5354
5.15k
                props, axisList[0], axisList[1], axisList[2]));
5355
5.15k
        }
5356
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5357
5.15k
    }
5358
4.78k
    if (csType == CS_TYPE_CARTESIAN) {
5359
4.18k
        if (axisList.size() == 2) {
5360
1.33k
            return cacheAndRet(
5361
1.33k
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5362
1.33k
        }
5363
2.84k
        if (axisList.size() == 3) {
5364
2.84k
            return cacheAndRet(cs::CartesianCS::create(
5365
2.84k
                props, axisList[0], axisList[1], axisList[2]));
5366
2.84k
        }
5367
0
        throw FactoryException("invalid number of axis for CartesianCS");
5368
2.84k
    }
5369
599
    if (csType == CS_TYPE_SPHERICAL) {
5370
11
        if (axisList.size() == 2) {
5371
11
            return cacheAndRet(
5372
11
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5373
11
        }
5374
0
        if (axisList.size() == 3) {
5375
0
            return cacheAndRet(cs::SphericalCS::create(
5376
0
                props, axisList[0], axisList[1], axisList[2]));
5377
0
        }
5378
0
        throw FactoryException("invalid number of axis for SphericalCS");
5379
0
    }
5380
588
    if (csType == CS_TYPE_VERTICAL) {
5381
588
        if (axisList.size() == 1) {
5382
588
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5383
588
        }
5384
0
        throw FactoryException("invalid number of axis for VerticalCS");
5385
588
    }
5386
0
    if (csType == CS_TYPE_ORDINAL) {
5387
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5388
0
    }
5389
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5390
0
}
5391
5392
// ---------------------------------------------------------------------------
5393
5394
/** \brief Returns a crs::GeodeticCRS from the specified code.
5395
 *
5396
 * @param code Object code allocated by authority.
5397
 * @return object.
5398
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5399
 * @throw FactoryException in case of other errors.
5400
 */
5401
5402
crs::GeodeticCRSNNPtr
5403
133k
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5404
133k
    return createGeodeticCRS(code, false);
5405
133k
}
5406
5407
// ---------------------------------------------------------------------------
5408
5409
/** \brief Returns a crs::GeographicCRS from the specified code.
5410
 *
5411
 * @param code Object code allocated by authority.
5412
 * @return object.
5413
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5414
 * @throw FactoryException in case of other errors.
5415
 */
5416
5417
crs::GeographicCRSNNPtr
5418
809
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5419
809
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5420
809
        createGeodeticCRS(code, true)));
5421
809
    if (!crs) {
5422
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5423
0
                                           d->authority(), code);
5424
0
    }
5425
809
    return NN_NO_CHECK(crs);
5426
809
}
5427
5428
// ---------------------------------------------------------------------------
5429
5430
static crs::GeodeticCRSNNPtr
5431
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5432
0
               const util::PropertyMap &props) {
5433
0
    auto cs = geodCRS->coordinateSystem();
5434
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5435
0
    if (ellipsoidalCS) {
5436
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5437
0
                                          geodCRS->datumEnsemble(),
5438
0
                                          NN_NO_CHECK(ellipsoidalCS));
5439
0
    }
5440
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5441
0
    if (geocentricCS) {
5442
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5443
0
                                        geodCRS->datumEnsemble(),
5444
0
                                        NN_NO_CHECK(geocentricCS));
5445
0
    }
5446
0
    return geodCRS;
5447
0
}
5448
5449
// ---------------------------------------------------------------------------
5450
5451
crs::GeodeticCRSNNPtr
5452
AuthorityFactory::createGeodeticCRS(const std::string &code,
5453
134k
                                    bool geographicOnly) const {
5454
134k
    const auto cacheKey(d->authority() + code);
5455
134k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5456
134k
    if (crs) {
5457
92.2k
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5458
92.2k
        if (geogCRS) {
5459
92.2k
            return NN_NO_CHECK(geogCRS);
5460
92.2k
        }
5461
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5462
0
                                           d->authority(), code);
5463
92.2k
    }
5464
42.4k
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5465
42.4k
                    "coordinate_system_code, datum_auth_name, datum_code, "
5466
42.4k
                    "text_definition, deprecated, description FROM "
5467
42.4k
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5468
42.4k
    if (geographicOnly) {
5469
27
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5470
27
               ")";
5471
27
    }
5472
42.4k
    auto res = d->runWithCodeParam(sql, code);
5473
42.4k
    if (res.empty()) {
5474
27
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5475
27
                                           d->authority(), code);
5476
27
    }
5477
42.3k
    try {
5478
42.3k
        const auto &row = res.front();
5479
42.3k
        const auto &name = row[0];
5480
42.3k
        const auto &type = row[1];
5481
42.3k
        const auto &cs_auth_name = row[2];
5482
42.3k
        const auto &cs_code = row[3];
5483
42.3k
        const auto &datum_auth_name = row[4];
5484
42.3k
        const auto &datum_code = row[5];
5485
42.3k
        const auto &text_definition = row[6];
5486
42.3k
        const bool deprecated = row[7] == "1";
5487
42.3k
        const auto &remarks = row[8];
5488
5489
42.3k
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5490
42.3k
                                                     deprecated, remarks);
5491
5492
42.3k
        if (!text_definition.empty()) {
5493
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5494
0
            auto obj = createFromUserInput(
5495
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5496
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5497
0
            if (geodCRS) {
5498
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5499
0
                d->context()->d->cache(cacheKey, crsRet);
5500
0
                return crsRet;
5501
0
            }
5502
5503
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5504
0
            if (boundCRS) {
5505
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5506
0
                    boundCRS->baseCRS());
5507
0
                if (geodCRS) {
5508
0
                    auto newBoundCRS = crs::BoundCRS::create(
5509
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5510
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5511
0
                    return NN_NO_CHECK(
5512
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5513
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5514
0
                }
5515
0
            }
5516
5517
0
            throw FactoryException(
5518
0
                "text_definition does not define a GeodeticCRS");
5519
0
        }
5520
5521
42.3k
        auto cs =
5522
42.3k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5523
42.3k
        datum::GeodeticReferenceFramePtr datum;
5524
42.3k
        datum::DatumEnsemblePtr datumEnsemble;
5525
42.3k
        constexpr bool turnEnsembleAsDatum = false;
5526
42.3k
        d->createFactory(datum_auth_name)
5527
42.3k
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5528
42.3k
                                            turnEnsembleAsDatum);
5529
5530
42.3k
        auto ellipsoidalCS =
5531
42.3k
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5532
42.3k
        if ((type == CRS_SUBTYPE_GEOG_2D || type == CRS_SUBTYPE_GEOG_3D) &&
5533
31.5k
            ellipsoidalCS) {
5534
31.5k
            auto crsRet = crs::GeographicCRS::create(
5535
31.5k
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5536
31.5k
            d->context()->d->cache(cacheKey, crsRet);
5537
31.5k
            return crsRet;
5538
31.5k
        }
5539
5540
10.8k
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5541
10.8k
        if (type == CRS_SUBTYPE_GEOCENTRIC && geocentricCS) {
5542
10.8k
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5543
10.8k
                                                   NN_NO_CHECK(geocentricCS));
5544
10.8k
            d->context()->d->cache(cacheKey, crsRet);
5545
10.8k
            return crsRet;
5546
10.8k
        }
5547
5548
18
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5549
18
        if (type == CRS_SUBTYPE_OTHER && sphericalCS) {
5550
18
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5551
18
                                                   NN_NO_CHECK(sphericalCS));
5552
18
            d->context()->d->cache(cacheKey, crsRet);
5553
18
            return crsRet;
5554
18
        }
5555
5556
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5557
0
                               type + ", " + cs->getWKT2Type(true));
5558
18
    } catch (const std::exception &ex) {
5559
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5560
0
    }
5561
42.3k
}
5562
5563
// ---------------------------------------------------------------------------
5564
5565
/** \brief Returns a crs::VerticalCRS from the specified code.
5566
 *
5567
 * @param code Object code allocated by authority.
5568
 * @return object.
5569
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5570
 * @throw FactoryException in case of other errors.
5571
 */
5572
5573
crs::VerticalCRSNNPtr
5574
1.31k
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5575
1.31k
    const auto cacheKey(d->authority() + code);
5576
1.31k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5577
1.31k
    if (crs) {
5578
197
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5579
197
        if (projCRS) {
5580
197
            return NN_NO_CHECK(projCRS);
5581
197
        }
5582
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5583
0
                                           d->authority(), code);
5584
197
    }
5585
1.12k
    auto res = d->runWithCodeParam(
5586
1.12k
        "SELECT name, coordinate_system_auth_name, "
5587
1.12k
        "coordinate_system_code, datum_auth_name, datum_code, "
5588
1.12k
        "deprecated FROM "
5589
1.12k
        "vertical_crs WHERE auth_name = ? AND code = ?",
5590
1.12k
        code);
5591
1.12k
    if (res.empty()) {
5592
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5593
0
                                           d->authority(), code);
5594
0
    }
5595
1.12k
    try {
5596
1.12k
        const auto &row = res.front();
5597
1.12k
        const auto &name = row[0];
5598
1.12k
        const auto &cs_auth_name = row[1];
5599
1.12k
        const auto &cs_code = row[2];
5600
1.12k
        const auto &datum_auth_name = row[3];
5601
1.12k
        const auto &datum_code = row[4];
5602
1.12k
        const bool deprecated = row[5] == "1";
5603
1.12k
        auto cs =
5604
1.12k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5605
1.12k
        datum::VerticalReferenceFramePtr datum;
5606
1.12k
        datum::DatumEnsemblePtr datumEnsemble;
5607
1.12k
        constexpr bool turnEnsembleAsDatum = false;
5608
1.12k
        d->createFactory(datum_auth_name)
5609
1.12k
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5610
1.12k
                                            turnEnsembleAsDatum);
5611
1.12k
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5612
1.12k
                                                     deprecated);
5613
5614
1.12k
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5615
1.12k
        if (verticalCS) {
5616
1.12k
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5617
1.12k
                                                   NN_NO_CHECK(verticalCS));
5618
1.12k
            d->context()->d->cache(cacheKey, crsRet);
5619
1.12k
            return crsRet;
5620
1.12k
        }
5621
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5622
0
                               cs->getWKT2Type(true));
5623
1.12k
    } catch (const std::exception &ex) {
5624
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5625
0
    }
5626
1.12k
}
5627
5628
// ---------------------------------------------------------------------------
5629
5630
/** \brief Returns a crs::EngineeringCRS from the specified code.
5631
 *
5632
 * @param code Object code allocated by authority.
5633
 * @return object.
5634
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5635
 * @throw FactoryException in case of other errors.
5636
 * @since 9.6
5637
 */
5638
5639
crs::EngineeringCRSNNPtr
5640
22
AuthorityFactory::createEngineeringCRS(const std::string &code) const {
5641
22
    const auto cacheKey(d->authority() + code);
5642
22
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5643
22
    if (crs) {
5644
0
        auto engCRS = std::dynamic_pointer_cast<crs::EngineeringCRS>(crs);
5645
0
        if (engCRS) {
5646
0
            return NN_NO_CHECK(engCRS);
5647
0
        }
5648
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5649
0
                                           d->authority(), code);
5650
0
    }
5651
22
    auto res = d->runWithCodeParam(
5652
22
        "SELECT name, coordinate_system_auth_name, "
5653
22
        "coordinate_system_code, datum_auth_name, datum_code, "
5654
22
        "deprecated FROM "
5655
22
        "engineering_crs WHERE auth_name = ? AND code = ?",
5656
22
        code);
5657
22
    if (res.empty()) {
5658
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5659
0
                                           d->authority(), code);
5660
0
    }
5661
22
    try {
5662
22
        const auto &row = res.front();
5663
22
        const auto &name = row[0];
5664
22
        const auto &cs_auth_name = row[1];
5665
22
        const auto &cs_code = row[2];
5666
22
        const auto &datum_auth_name = row[3];
5667
22
        const auto &datum_code = row[4];
5668
22
        const bool deprecated = row[5] == "1";
5669
22
        auto cs =
5670
22
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5671
22
        auto datum = d->createFactory(datum_auth_name)
5672
22
                         ->createEngineeringDatum(datum_code);
5673
22
        auto props = d->createPropertiesSearchUsages("engineering_crs", code,
5674
22
                                                     name, deprecated);
5675
22
        auto crsRet = crs::EngineeringCRS::create(props, datum, cs);
5676
22
        d->context()->d->cache(cacheKey, crsRet);
5677
22
        return crsRet;
5678
22
    } catch (const std::exception &ex) {
5679
0
        throw buildFactoryException("engineeringCRS", d->authority(), code, ex);
5680
0
    }
5681
22
}
5682
5683
// ---------------------------------------------------------------------------
5684
5685
/** \brief Returns a operation::Conversion from the specified code.
5686
 *
5687
 * @param code Object code allocated by authority.
5688
 * @return object.
5689
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5690
 * @throw FactoryException in case of other errors.
5691
 */
5692
5693
operation::ConversionNNPtr
5694
3.78k
AuthorityFactory::createConversion(const std::string &code) const {
5695
5696
3.78k
    static const char *sql =
5697
3.78k
        "SELECT name, description, "
5698
3.78k
        "method_auth_name, method_code, method_name, "
5699
5700
3.78k
        "param1_auth_name, param1_code, param1_name, param1_value, "
5701
3.78k
        "param1_uom_auth_name, param1_uom_code, "
5702
5703
3.78k
        "param2_auth_name, param2_code, param2_name, param2_value, "
5704
3.78k
        "param2_uom_auth_name, param2_uom_code, "
5705
5706
3.78k
        "param3_auth_name, param3_code, param3_name, param3_value, "
5707
3.78k
        "param3_uom_auth_name, param3_uom_code, "
5708
5709
3.78k
        "param4_auth_name, param4_code, param4_name, param4_value, "
5710
3.78k
        "param4_uom_auth_name, param4_uom_code, "
5711
5712
3.78k
        "param5_auth_name, param5_code, param5_name, param5_value, "
5713
3.78k
        "param5_uom_auth_name, param5_uom_code, "
5714
5715
3.78k
        "param6_auth_name, param6_code, param6_name, param6_value, "
5716
3.78k
        "param6_uom_auth_name, param6_uom_code, "
5717
5718
3.78k
        "param7_auth_name, param7_code, param7_name, param7_value, "
5719
3.78k
        "param7_uom_auth_name, param7_uom_code, "
5720
5721
3.78k
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5722
5723
3.78k
    auto res = d->runWithCodeParam(sql, code);
5724
3.78k
    if (res.empty()) {
5725
0
        try {
5726
            // Conversions using methods Change of Vertical Unit or
5727
            // Height Depth Reversal are stored in other_transformation
5728
0
            auto op = createCoordinateOperation(
5729
0
                code, false /* allowConcatenated */,
5730
0
                false /* usePROJAlternativeGridNames */,
5731
0
                "other_transformation");
5732
0
            auto conv =
5733
0
                util::nn_dynamic_pointer_cast<operation::Conversion>(op);
5734
0
            if (conv) {
5735
0
                return NN_NO_CHECK(conv);
5736
0
            }
5737
0
        } catch (const std::exception &) {
5738
0
        }
5739
0
        throw NoSuchAuthorityCodeException("conversion not found",
5740
0
                                           d->authority(), code);
5741
0
    }
5742
3.78k
    try {
5743
3.78k
        const auto &row = res.front();
5744
3.78k
        size_t idx = 0;
5745
3.78k
        const auto &name = row[idx++];
5746
3.78k
        const auto &description = row[idx++];
5747
3.78k
        const auto &method_auth_name = row[idx++];
5748
3.78k
        const auto &method_code = row[idx++];
5749
3.78k
        const auto &method_name = row[idx++];
5750
3.78k
        const size_t base_param_idx = idx;
5751
3.78k
        std::vector<operation::OperationParameterNNPtr> parameters;
5752
3.78k
        std::vector<operation::ParameterValueNNPtr> values;
5753
22.8k
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5754
22.7k
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5755
22.7k
            if (param_auth_name.empty()) {
5756
3.72k
                break;
5757
3.72k
            }
5758
19.0k
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5759
19.0k
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5760
19.0k
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5761
19.0k
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5762
19.0k
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5763
19.0k
            parameters.emplace_back(operation::OperationParameter::create(
5764
19.0k
                util::PropertyMap()
5765
19.0k
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5766
19.0k
                    .set(metadata::Identifier::CODE_KEY, param_code)
5767
19.0k
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5768
19.0k
            std::string normalized_uom_code(param_uom_code);
5769
19.0k
            const double normalized_value = normalizeMeasure(
5770
19.0k
                param_uom_code, param_value, normalized_uom_code);
5771
19.0k
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5772
19.0k
                                              normalized_uom_code);
5773
19.0k
            values.emplace_back(operation::ParameterValue::create(
5774
19.0k
                common::Measure(normalized_value, uom)));
5775
19.0k
        }
5776
3.78k
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5777
5778
3.78k
        auto propConversion = d->createPropertiesSearchUsages(
5779
3.78k
            "conversion", code, name, deprecated);
5780
3.78k
        if (!description.empty())
5781
1.43k
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5782
1.43k
                               description);
5783
5784
3.78k
        auto propMethod = util::PropertyMap().set(
5785
3.78k
            common::IdentifiedObject::NAME_KEY, method_name);
5786
3.78k
        if (!method_auth_name.empty()) {
5787
3.78k
            propMethod
5788
3.78k
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5789
3.78k
                .set(metadata::Identifier::CODE_KEY, method_code);
5790
3.78k
        }
5791
5792
3.78k
        return operation::Conversion::create(propConversion, propMethod,
5793
3.78k
                                             parameters, values);
5794
3.78k
    } catch (const std::exception &ex) {
5795
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5796
0
    }
5797
3.78k
}
5798
5799
// ---------------------------------------------------------------------------
5800
5801
/** \brief Returns a crs::ProjectedCRS from the specified code.
5802
 *
5803
 * @param code Object code allocated by authority.
5804
 * @return object.
5805
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5806
 * @throw FactoryException in case of other errors.
5807
 */
5808
5809
crs::ProjectedCRSNNPtr
5810
3.64k
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5811
3.64k
    const auto cacheKey(d->authority() + code);
5812
3.64k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5813
3.64k
    if (crs) {
5814
65
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5815
65
        if (projCRS) {
5816
65
            return NN_NO_CHECK(projCRS);
5817
65
        }
5818
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5819
0
                                           d->authority(), code);
5820
65
    }
5821
3.58k
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5822
3.64k
}
5823
5824
// ---------------------------------------------------------------------------
5825
//! @cond Doxygen_Suppress
5826
5827
/** Returns the result of the SQL query needed by createProjectedCRSEnd
5828
 *
5829
 * The split in two functions is for createFromCoordinateReferenceSystemCodes()
5830
 * convenience, to avoid throwing exceptions.
5831
 */
5832
SQLResultSet
5833
3.58k
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5834
3.58k
    return runWithCodeParam(
5835
3.58k
        "SELECT name, coordinate_system_auth_name, "
5836
3.58k
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5837
3.58k
        "conversion_auth_name, conversion_code, "
5838
3.58k
        "text_definition, "
5839
3.58k
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5840
3.58k
        code);
5841
3.58k
}
5842
5843
// ---------------------------------------------------------------------------
5844
5845
/** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
5846
crs::ProjectedCRSNNPtr
5847
AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
5848
3.58k
                                                 const SQLResultSet &res) {
5849
3.58k
    const auto cacheKey(authority() + code);
5850
3.58k
    if (res.empty()) {
5851
8
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5852
8
                                           authority(), code);
5853
8
    }
5854
3.57k
    try {
5855
3.57k
        const auto &row = res.front();
5856
3.57k
        const auto &name = row[0];
5857
3.57k
        const auto &cs_auth_name = row[1];
5858
3.57k
        const auto &cs_code = row[2];
5859
3.57k
        const auto &geodetic_crs_auth_name = row[3];
5860
3.57k
        const auto &geodetic_crs_code = row[4];
5861
3.57k
        const auto &conversion_auth_name = row[5];
5862
3.57k
        const auto &conversion_code = row[6];
5863
3.57k
        const auto &text_definition = row[7];
5864
3.57k
        const bool deprecated = row[8] == "1";
5865
5866
3.57k
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5867
3.57k
                                                  deprecated);
5868
5869
3.57k
        if (!text_definition.empty()) {
5870
307
            DatabaseContext::Private::RecursionDetector detector(context());
5871
307
            auto obj = createFromUserInput(
5872
307
                pj_add_type_crs_if_needed(text_definition), context());
5873
307
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5874
307
            if (projCRS) {
5875
307
                auto conv = projCRS->derivingConversion();
5876
307
                auto newConv =
5877
307
                    (conv->nameStr() == "unnamed")
5878
307
                        ? operation::Conversion::create(
5879
294
                              util::PropertyMap().set(
5880
294
                                  common::IdentifiedObject::NAME_KEY, name),
5881
294
                              conv->method(), conv->parameterValues())
5882
307
                        : std::move(conv);
5883
307
                auto crsRet = crs::ProjectedCRS::create(
5884
307
                    props, projCRS->baseCRS(), newConv,
5885
307
                    projCRS->coordinateSystem());
5886
307
                context()->d->cache(cacheKey, crsRet);
5887
307
                return crsRet;
5888
307
            }
5889
5890
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5891
0
            if (boundCRS) {
5892
0
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
5893
0
                    boundCRS->baseCRS().get());
5894
0
                if (projCRS) {
5895
0
                    auto newBoundCRS = crs::BoundCRS::create(
5896
0
                        crs::ProjectedCRS::create(props, projCRS->baseCRS(),
5897
0
                                                  projCRS->derivingConversion(),
5898
0
                                                  projCRS->coordinateSystem()),
5899
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5900
0
                    return NN_NO_CHECK(
5901
0
                        util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
5902
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5903
0
                }
5904
0
            }
5905
5906
0
            throw FactoryException(
5907
0
                "text_definition does not define a ProjectedCRS");
5908
0
        }
5909
5910
3.26k
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5911
5912
3.26k
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5913
3.26k
                           ->createGeodeticCRS(geodetic_crs_code);
5914
5915
3.26k
        auto conv = createFactory(conversion_auth_name)
5916
3.26k
                        ->createConversion(conversion_code);
5917
3.26k
        if (conv->nameStr() == "unnamed") {
5918
456
            conv = conv->shallowClone();
5919
456
            conv->setProperties(util::PropertyMap().set(
5920
456
                common::IdentifiedObject::NAME_KEY, name));
5921
456
        }
5922
5923
3.26k
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5924
3.26k
        if (cartesianCS) {
5925
3.26k
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5926
3.26k
                                                    NN_NO_CHECK(cartesianCS));
5927
3.26k
            context()->d->cache(cacheKey, crsRet);
5928
3.26k
            return crsRet;
5929
3.26k
        }
5930
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5931
0
                               cs->getWKT2Type(true));
5932
3.26k
    } catch (const std::exception &ex) {
5933
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5934
0
    }
5935
3.57k
}
5936
//! @endcond
5937
5938
// ---------------------------------------------------------------------------
5939
5940
/** \brief Returns a crs::DerivedProjectedCRS from the specified code.
5941
 *
5942
 * @param code Object code allocated by authority.
5943
 * @return object.
5944
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5945
 * @throw FactoryException in case of other errors.
5946
 */
5947
crs::DerivedProjectedCRSNNPtr
5948
0
AuthorityFactory::createDerivedProjectedCRS(const std::string &code) const {
5949
0
    const auto cacheKey(d->authority() + code);
5950
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5951
0
    if (crs) {
5952
0
        auto derivedProjCRS =
5953
0
            std::dynamic_pointer_cast<crs::DerivedProjectedCRS>(crs);
5954
0
        if (derivedProjCRS) {
5955
0
            return NN_NO_CHECK(derivedProjCRS);
5956
0
        }
5957
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5958
0
                                           d->authority(), code);
5959
0
    }
5960
0
    return d->createDerivedProjectedCRSEnd(
5961
0
        code, d->createDerivedProjectedCRSBegin(code));
5962
0
}
5963
5964
// ---------------------------------------------------------------------------
5965
//! @cond Doxygen_Suppress
5966
5967
/** Returns the result of the SQL query needed by createDerivedProjectedCRSEnd
5968
 */
5969
SQLResultSet AuthorityFactory::Private::createDerivedProjectedCRSBegin(
5970
0
    const std::string &code) {
5971
0
    return runWithCodeParam(
5972
0
        "SELECT name, coordinate_system_auth_name, "
5973
0
        "coordinate_system_code, base_crs_auth_name, base_crs_code, "
5974
0
        "conversion_auth_name, conversion_code, "
5975
0
        "text_definition, "
5976
0
        "deprecated FROM derived_projected_crs WHERE auth_name = ? AND code = "
5977
0
        "?",
5978
0
        code);
5979
0
}
5980
5981
// ---------------------------------------------------------------------------
5982
5983
/** Build a DerivedProjectedCRS from the result of
5984
 * createDerivedProjectedCRSBegin() */
5985
crs::DerivedProjectedCRSNNPtr
5986
AuthorityFactory::Private::createDerivedProjectedCRSEnd(
5987
0
    const std::string &code, const SQLResultSet &res) {
5988
0
    const auto cacheKey(authority() + code);
5989
0
    if (res.empty()) {
5990
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5991
0
                                           authority(), code);
5992
0
    }
5993
0
    try {
5994
0
        const auto &row = res.front();
5995
0
        const auto &name = row[0];
5996
0
        const auto &cs_auth_name = row[1];
5997
0
        const auto &cs_code = row[2];
5998
0
        const auto &base_crs_auth_name = row[3];
5999
0
        const auto &base_crs_code = row[4];
6000
0
        const auto &conversion_auth_name = row[5];
6001
0
        const auto &conversion_code = row[6];
6002
0
        const auto &text_definition = row[7];
6003
0
        const bool deprecated = row[8] == "1";
6004
6005
0
        auto props = createPropertiesSearchUsages("derived_projected_crs", code,
6006
0
                                                  name, deprecated);
6007
6008
0
        if (!text_definition.empty()) {
6009
0
            DatabaseContext::Private::RecursionDetector detector(context());
6010
0
            auto obj = createFromUserInput(
6011
0
                pj_add_type_crs_if_needed(text_definition), context());
6012
0
            auto derivedProjCRS =
6013
0
                dynamic_cast<const crs::DerivedProjectedCRS *>(obj.get());
6014
0
            if (derivedProjCRS) {
6015
0
                auto conv = derivedProjCRS->derivingConversion();
6016
0
                auto newConv =
6017
0
                    (conv->nameStr() == "unnamed")
6018
0
                        ? operation::Conversion::create(
6019
0
                              util::PropertyMap().set(
6020
0
                                  common::IdentifiedObject::NAME_KEY, name),
6021
0
                              conv->method(), conv->parameterValues())
6022
0
                        : std::move(conv);
6023
0
                auto crsRet = crs::DerivedProjectedCRS::create(
6024
0
                    props, derivedProjCRS->baseCRS(), newConv,
6025
0
                    derivedProjCRS->coordinateSystem());
6026
0
                context()->d->cache(cacheKey, crsRet);
6027
0
                return crsRet;
6028
0
            }
6029
0
            throw FactoryException(
6030
0
                "text_definition does not define a DerivedProjectedCRS");
6031
0
        }
6032
6033
0
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
6034
6035
0
        auto baseCRS = createFactory(base_crs_auth_name)
6036
0
                           ->createProjectedCRS(base_crs_code);
6037
6038
0
        auto conv = createFactory(conversion_auth_name)
6039
0
                        ->createConversion(conversion_code);
6040
0
        if (conv->nameStr() == "unnamed") {
6041
0
            conv = conv->shallowClone();
6042
0
            conv->setProperties(util::PropertyMap().set(
6043
0
                common::IdentifiedObject::NAME_KEY, name));
6044
0
        }
6045
6046
0
        auto crsRet =
6047
0
            crs::DerivedProjectedCRS::create(props, baseCRS, conv, cs);
6048
0
        context()->d->cache(cacheKey, crsRet);
6049
0
        return crsRet;
6050
0
    } catch (const std::exception &ex) {
6051
0
        throw buildFactoryException("derivedProjectedCRS", authority(), code,
6052
0
                                    ex);
6053
0
    }
6054
0
}
6055
//! @endcond
6056
6057
// ---------------------------------------------------------------------------
6058
6059
/** \brief Returns a crs::CompoundCRS from the specified code.
6060
 *
6061
 * @param code Object code allocated by authority.
6062
 * @return object.
6063
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6064
 * @throw FactoryException in case of other errors.
6065
 */
6066
6067
crs::CompoundCRSNNPtr
6068
160
AuthorityFactory::createCompoundCRS(const std::string &code) const {
6069
160
    auto res =
6070
160
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
6071
160
                            "vertical_crs_auth_name, vertical_crs_code, "
6072
160
                            "deprecated FROM "
6073
160
                            "compound_crs WHERE auth_name = ? AND code = ?",
6074
160
                            code);
6075
160
    if (res.empty()) {
6076
2
        throw NoSuchAuthorityCodeException("compoundCRS not found",
6077
2
                                           d->authority(), code);
6078
2
    }
6079
158
    try {
6080
158
        const auto &row = res.front();
6081
158
        const auto &name = row[0];
6082
158
        const auto &horiz_crs_auth_name = row[1];
6083
158
        const auto &horiz_crs_code = row[2];
6084
158
        const auto &vertical_crs_auth_name = row[3];
6085
158
        const auto &vertical_crs_code = row[4];
6086
158
        const bool deprecated = row[5] == "1";
6087
6088
158
        auto horizCRS =
6089
158
            d->createFactory(horiz_crs_auth_name)
6090
158
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
6091
158
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
6092
158
                           ->createVerticalCRS(vertical_crs_code);
6093
6094
158
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
6095
158
                                                     deprecated);
6096
158
        return crs::CompoundCRS::create(
6097
158
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
6098
158
                                              std::move(vertCRS)});
6099
158
    } catch (const std::exception &ex) {
6100
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
6101
0
    }
6102
158
}
6103
6104
// ---------------------------------------------------------------------------
6105
6106
/** \brief Returns a crs::CRS from the specified code.
6107
 *
6108
 * @param code Object code allocated by authority.
6109
 * @return object.
6110
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6111
 * @throw FactoryException in case of other errors.
6112
 */
6113
6114
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
6115
560k
    const std::string &code) const {
6116
560k
    return createCoordinateReferenceSystem(code, true);
6117
560k
}
6118
6119
//! @cond Doxygen_Suppress
6120
6121
crs::CRSNNPtr
6122
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
6123
560k
                                                  bool allowCompound) const {
6124
560k
    const auto cacheKey(d->authority() + code);
6125
560k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
6126
560k
    if (crs) {
6127
553k
        return NN_NO_CHECK(crs);
6128
553k
    }
6129
6130
7.24k
    if (d->authority() == metadata::Identifier::OGC) {
6131
23
        if (code == "AnsiDate") {
6132
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
6133
0
            return crs::TemporalCRS::create(
6134
0
                util::PropertyMap()
6135
                    // above URL indicates Julian Date" as name... likely wrong
6136
0
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
6137
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6138
0
                    .set(metadata::Identifier::CODE_KEY, code),
6139
0
                datum::TemporalDatum::create(
6140
0
                    util::PropertyMap().set(
6141
0
                        common::IdentifiedObject::NAME_KEY,
6142
0
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
6143
0
                        "as day 1."),
6144
0
                    common::DateTime::create("1600-12-31T00:00:00Z"),
6145
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6146
0
                cs::TemporalCountCS::create(
6147
0
                    util::PropertyMap(),
6148
0
                    cs::CoordinateSystemAxis::create(
6149
0
                        util::PropertyMap().set(
6150
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6151
0
                        "T", cs::AxisDirection::FUTURE,
6152
0
                        common::UnitOfMeasure("day", 0,
6153
0
                                              UnitOfMeasure::Type::TIME))));
6154
0
        }
6155
23
        if (code == "JulianDate") {
6156
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
6157
0
            return crs::TemporalCRS::create(
6158
0
                util::PropertyMap()
6159
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
6160
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6161
0
                    .set(metadata::Identifier::CODE_KEY, code),
6162
0
                datum::TemporalDatum::create(
6163
0
                    util::PropertyMap().set(
6164
0
                        common::IdentifiedObject::NAME_KEY,
6165
0
                        "The beginning of the Julian period."),
6166
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
6167
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6168
0
                cs::TemporalCountCS::create(
6169
0
                    util::PropertyMap(),
6170
0
                    cs::CoordinateSystemAxis::create(
6171
0
                        util::PropertyMap().set(
6172
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6173
0
                        "T", cs::AxisDirection::FUTURE,
6174
0
                        common::UnitOfMeasure("day", 0,
6175
0
                                              UnitOfMeasure::Type::TIME))));
6176
0
        }
6177
23
        if (code == "UnixTime") {
6178
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
6179
0
            return crs::TemporalCRS::create(
6180
0
                util::PropertyMap()
6181
0
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
6182
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6183
0
                    .set(metadata::Identifier::CODE_KEY, code),
6184
0
                datum::TemporalDatum::create(
6185
0
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6186
0
                                            "Unix epoch"),
6187
0
                    common::DateTime::create("1970-01-01T00:00:00Z"),
6188
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6189
0
                cs::TemporalCountCS::create(
6190
0
                    util::PropertyMap(),
6191
0
                    cs::CoordinateSystemAxis::create(
6192
0
                        util::PropertyMap().set(
6193
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6194
0
                        "T", cs::AxisDirection::FUTURE,
6195
0
                        common::UnitOfMeasure::SECOND)));
6196
0
        }
6197
23
        if (code == "84") {
6198
0
            return createCoordinateReferenceSystem("CRS84", false);
6199
0
        }
6200
23
    }
6201
6202
7.24k
    auto res = d->runWithCodeParam(
6203
7.24k
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
6204
7.24k
    if (res.empty()) {
6205
555
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
6206
555
                                           code);
6207
555
    }
6208
6.68k
    const auto &type = res.front()[0];
6209
6.68k
    if (type == CRS_SUBTYPE_GEOG_2D || type == CRS_SUBTYPE_GEOG_3D ||
6210
6.42k
        type == CRS_SUBTYPE_GEOCENTRIC || type == CRS_SUBTYPE_OTHER) {
6211
6.42k
        return createGeodeticCRS(code);
6212
6.42k
    }
6213
268
    if (type == CRS_SUBTYPE_VERTICAL) {
6214
142
        return createVerticalCRS(code);
6215
142
    }
6216
126
    if (type == CRS_SUBTYPE_PROJECTED) {
6217
90
        return createProjectedCRS(code);
6218
90
    }
6219
36
    if (type == CRS_SUBTYPE_DERIVED_PROJECTED) {
6220
0
        return createDerivedProjectedCRS(code);
6221
0
    }
6222
36
    if (type == CRS_SUBTYPE_ENGINEERING) {
6223
3
        return createEngineeringCRS(code);
6224
3
    }
6225
33
    if (allowCompound && type == CRS_SUBTYPE_COMPOUND) {
6226
33
        return createCompoundCRS(code);
6227
33
    }
6228
0
    throw FactoryException("unhandled CRS type: " + type);
6229
33
}
6230
6231
//! @endcond
6232
6233
// ---------------------------------------------------------------------------
6234
6235
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
6236
 *
6237
 * @param code Object code allocated by authority.
6238
 * @return object.
6239
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6240
 * @throw FactoryException in case of other errors.
6241
 * @since 9.4
6242
 */
6243
6244
coordinates::CoordinateMetadataNNPtr
6245
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
6246
0
    auto res = d->runWithCodeParam(
6247
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
6248
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
6249
0
        code);
6250
0
    if (res.empty()) {
6251
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
6252
0
                                           d->authority(), code);
6253
0
    }
6254
0
    try {
6255
0
        const auto &row = res.front();
6256
0
        const auto &crs_auth_name = row[0];
6257
0
        const auto &crs_code = row[1];
6258
0
        const auto &crs_text_definition = row[2];
6259
0
        const auto &coordinate_epoch = row[3];
6260
6261
0
        auto l_context = d->context();
6262
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
6263
0
        auto crs =
6264
0
            !crs_auth_name.empty()
6265
0
                ? d->createFactory(crs_auth_name)
6266
0
                      ->createCoordinateReferenceSystem(crs_code)
6267
0
                      .as_nullable()
6268
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
6269
0
                      createFromUserInput(crs_text_definition, l_context));
6270
0
        if (!crs) {
6271
0
            throw FactoryException(
6272
0
                std::string("cannot build CoordinateMetadata ") +
6273
0
                d->authority() + ":" + code + ": cannot build CRS");
6274
0
        }
6275
0
        if (coordinate_epoch.empty()) {
6276
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
6277
0
        } else {
6278
0
            return coordinates::CoordinateMetadata::create(
6279
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
6280
0
                l_context.as_nullable());
6281
0
        }
6282
0
    } catch (const std::exception &ex) {
6283
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
6284
0
                                    ex);
6285
0
    }
6286
0
}
6287
6288
// ---------------------------------------------------------------------------
6289
6290
//! @cond Doxygen_Suppress
6291
6292
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
6293
120k
                                               int code) {
6294
120k
    return util::PropertyMap()
6295
120k
        .set(common::IdentifiedObject::NAME_KEY, name)
6296
120k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
6297
120k
        .set(metadata::Identifier::CODE_KEY, code);
6298
120k
}
6299
6300
// ---------------------------------------------------------------------------
6301
6302
120k
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
6303
120k
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
6304
120k
    assert(name);
6305
120k
    return operation::OperationParameter::create(
6306
120k
        createMapNameEPSGCode(name, code));
6307
120k
}
6308
6309
static operation::ParameterValueNNPtr createLength(const std::string &value,
6310
99.8k
                                                   const UnitOfMeasure &uom) {
6311
99.8k
    return operation::ParameterValue::create(
6312
99.8k
        common::Length(c_locale_stod(value), uom));
6313
99.8k
}
6314
6315
static operation::ParameterValueNNPtr createAngle(const std::string &value,
6316
14.6k
                                                  const UnitOfMeasure &uom) {
6317
14.6k
    return operation::ParameterValue::create(
6318
14.6k
        common::Angle(c_locale_stod(value), uom));
6319
14.6k
}
6320
6321
//! @endcond
6322
6323
// ---------------------------------------------------------------------------
6324
6325
/** \brief Returns a operation::CoordinateOperation from the specified code.
6326
 *
6327
 * @param code Object code allocated by authority.
6328
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6329
 * should be substituted to the official grid names.
6330
 * @return object.
6331
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6332
 * @throw FactoryException in case of other errors.
6333
 */
6334
6335
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6336
1.01k
    const std::string &code, bool usePROJAlternativeGridNames) const {
6337
1.01k
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
6338
1.01k
                                     std::string());
6339
1.01k
}
6340
6341
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6342
    const std::string &code, bool allowConcatenated,
6343
172k
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
6344
172k
    std::string type(typeIn);
6345
172k
    if (type.empty()) {
6346
73.9k
        auto res = d->runWithCodeParam(
6347
73.9k
            "SELECT type FROM coordinate_operation_with_conversion_view "
6348
73.9k
            "WHERE auth_name = ? AND code = ?",
6349
73.9k
            code);
6350
73.9k
        if (res.empty()) {
6351
1
            throw NoSuchAuthorityCodeException("coordinate operation not found",
6352
1
                                               d->authority(), code);
6353
1
        }
6354
73.9k
        type = res.front()[0];
6355
73.9k
    }
6356
6357
172k
    if (type == "conversion") {
6358
30
        return createConversion(code);
6359
30
    }
6360
6361
172k
    if (type == "helmert_transformation") {
6362
6363
32.3k
        auto res = d->runWithCodeParam(
6364
32.3k
            "SELECT name, description, "
6365
32.3k
            "method_auth_name, method_code, method_name, "
6366
32.3k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6367
32.3k
            "target_crs_code, "
6368
32.3k
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
6369
32.3k
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
6370
32.3k
            "rotation_uom_code, scale_difference, "
6371
32.3k
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
6372
32.3k
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
6373
32.3k
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
6374
32.3k
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
6375
32.3k
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
6376
32.3k
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
6377
32.3k
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
6378
32.3k
            "operation_version, deprecated FROM "
6379
32.3k
            "helmert_transformation WHERE auth_name = ? AND code = ?",
6380
32.3k
            code);
6381
32.3k
        if (res.empty()) {
6382
            // shouldn't happen if foreign keys are OK
6383
0
            throw NoSuchAuthorityCodeException(
6384
0
                "helmert_transformation not found", d->authority(), code);
6385
0
        }
6386
32.3k
        try {
6387
32.3k
            const auto &row = res.front();
6388
32.3k
            size_t idx = 0;
6389
32.3k
            const auto &name = row[idx++];
6390
32.3k
            const auto &description = row[idx++];
6391
32.3k
            const auto &method_auth_name = row[idx++];
6392
32.3k
            const auto &method_code = row[idx++];
6393
32.3k
            const auto &method_name = row[idx++];
6394
32.3k
            const auto &source_crs_auth_name = row[idx++];
6395
32.3k
            const auto &source_crs_code = row[idx++];
6396
32.3k
            const auto &target_crs_auth_name = row[idx++];
6397
32.3k
            const auto &target_crs_code = row[idx++];
6398
32.3k
            const auto &accuracy = row[idx++];
6399
6400
32.3k
            const auto &tx = row[idx++];
6401
32.3k
            const auto &ty = row[idx++];
6402
32.3k
            const auto &tz = row[idx++];
6403
32.3k
            const auto &translation_uom_auth_name = row[idx++];
6404
32.3k
            const auto &translation_uom_code = row[idx++];
6405
32.3k
            const auto &rx = row[idx++];
6406
32.3k
            const auto &ry = row[idx++];
6407
32.3k
            const auto &rz = row[idx++];
6408
32.3k
            const auto &rotation_uom_auth_name = row[idx++];
6409
32.3k
            const auto &rotation_uom_code = row[idx++];
6410
32.3k
            const auto &scale_difference = row[idx++];
6411
32.3k
            const auto &scale_difference_uom_auth_name = row[idx++];
6412
32.3k
            const auto &scale_difference_uom_code = row[idx++];
6413
6414
32.3k
            const auto &rate_tx = row[idx++];
6415
32.3k
            const auto &rate_ty = row[idx++];
6416
32.3k
            const auto &rate_tz = row[idx++];
6417
32.3k
            const auto &rate_translation_uom_auth_name = row[idx++];
6418
32.3k
            const auto &rate_translation_uom_code = row[idx++];
6419
32.3k
            const auto &rate_rx = row[idx++];
6420
32.3k
            const auto &rate_ry = row[idx++];
6421
32.3k
            const auto &rate_rz = row[idx++];
6422
32.3k
            const auto &rate_rotation_uom_auth_name = row[idx++];
6423
32.3k
            const auto &rate_rotation_uom_code = row[idx++];
6424
32.3k
            const auto &rate_scale_difference = row[idx++];
6425
32.3k
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
6426
32.3k
            const auto &rate_scale_difference_uom_code = row[idx++];
6427
6428
32.3k
            const auto &epoch = row[idx++];
6429
32.3k
            const auto &epoch_uom_auth_name = row[idx++];
6430
32.3k
            const auto &epoch_uom_code = row[idx++];
6431
6432
32.3k
            const auto &px = row[idx++];
6433
32.3k
            const auto &py = row[idx++];
6434
32.3k
            const auto &pz = row[idx++];
6435
32.3k
            const auto &pivot_uom_auth_name = row[idx++];
6436
32.3k
            const auto &pivot_uom_code = row[idx++];
6437
6438
32.3k
            const auto &operation_version = row[idx++];
6439
32.3k
            const auto &deprecated_str = row[idx++];
6440
32.3k
            const bool deprecated = deprecated_str == "1";
6441
32.3k
            assert(idx == row.size());
6442
6443
32.3k
            auto uom_translation = d->createUnitOfMeasure(
6444
32.3k
                translation_uom_auth_name, translation_uom_code);
6445
6446
32.3k
            auto uom_epoch = epoch_uom_auth_name.empty()
6447
32.3k
                                 ? common::UnitOfMeasure::NONE
6448
32.3k
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
6449
1.06k
                                                          epoch_uom_code);
6450
6451
32.3k
            auto sourceCRS =
6452
32.3k
                d->createFactory(source_crs_auth_name)
6453
32.3k
                    ->createCoordinateReferenceSystem(source_crs_code);
6454
32.3k
            auto targetCRS =
6455
32.3k
                d->createFactory(target_crs_auth_name)
6456
32.3k
                    ->createCoordinateReferenceSystem(target_crs_code);
6457
6458
32.3k
            std::vector<operation::OperationParameterNNPtr> parameters;
6459
32.3k
            std::vector<operation::ParameterValueNNPtr> values;
6460
6461
32.3k
            parameters.emplace_back(createOpParamNameEPSGCode(
6462
32.3k
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
6463
32.3k
            values.emplace_back(createLength(tx, uom_translation));
6464
6465
32.3k
            parameters.emplace_back(createOpParamNameEPSGCode(
6466
32.3k
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
6467
32.3k
            values.emplace_back(createLength(ty, uom_translation));
6468
6469
32.3k
            parameters.emplace_back(createOpParamNameEPSGCode(
6470
32.3k
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
6471
32.3k
            values.emplace_back(createLength(tz, uom_translation));
6472
6473
32.3k
            if (!rx.empty()) {
6474
                // Helmert 7-, 8-, 10- or 15- parameter cases
6475
3.99k
                auto uom_rotation = d->createUnitOfMeasure(
6476
3.99k
                    rotation_uom_auth_name, rotation_uom_code);
6477
6478
3.99k
                parameters.emplace_back(createOpParamNameEPSGCode(
6479
3.99k
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
6480
3.99k
                values.emplace_back(createAngle(rx, uom_rotation));
6481
6482
3.99k
                parameters.emplace_back(createOpParamNameEPSGCode(
6483
3.99k
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
6484
3.99k
                values.emplace_back(createAngle(ry, uom_rotation));
6485
6486
3.99k
                parameters.emplace_back(createOpParamNameEPSGCode(
6487
3.99k
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
6488
3.99k
                values.emplace_back(createAngle(rz, uom_rotation));
6489
6490
3.99k
                auto uom_scale_difference =
6491
3.99k
                    scale_difference_uom_auth_name.empty()
6492
3.99k
                        ? common::UnitOfMeasure::NONE
6493
3.99k
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
6494
3.99k
                                                 scale_difference_uom_code);
6495
6496
3.99k
                parameters.emplace_back(createOpParamNameEPSGCode(
6497
3.99k
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
6498
3.99k
                values.emplace_back(operation::ParameterValue::create(
6499
3.99k
                    common::Scale(c_locale_stod(scale_difference),
6500
3.99k
                                  uom_scale_difference)));
6501
3.99k
            }
6502
6503
32.3k
            if (!rate_tx.empty()) {
6504
                // Helmert 15-parameter
6505
6506
883
                auto uom_rate_translation = d->createUnitOfMeasure(
6507
883
                    rate_translation_uom_auth_name, rate_translation_uom_code);
6508
6509
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6510
883
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
6511
883
                values.emplace_back(
6512
883
                    createLength(rate_tx, uom_rate_translation));
6513
6514
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6515
883
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6516
883
                values.emplace_back(
6517
883
                    createLength(rate_ty, uom_rate_translation));
6518
6519
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6520
883
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6521
883
                values.emplace_back(
6522
883
                    createLength(rate_tz, uom_rate_translation));
6523
6524
883
                auto uom_rate_rotation = d->createUnitOfMeasure(
6525
883
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6526
6527
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6528
883
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6529
883
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6530
6531
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6532
883
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6533
883
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6534
6535
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6536
883
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6537
883
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6538
6539
883
                auto uom_rate_scale_difference =
6540
883
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6541
883
                                           rate_scale_difference_uom_code);
6542
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6543
883
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6544
883
                values.emplace_back(operation::ParameterValue::create(
6545
883
                    common::Scale(c_locale_stod(rate_scale_difference),
6546
883
                                  uom_rate_scale_difference)));
6547
6548
883
                parameters.emplace_back(createOpParamNameEPSGCode(
6549
883
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6550
883
                values.emplace_back(operation::ParameterValue::create(
6551
883
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6552
31.4k
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6553
                // Helmert 8-parameter
6554
186
                parameters.emplace_back(createOpParamNameEPSGCode(
6555
186
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6556
186
                values.emplace_back(operation::ParameterValue::create(
6557
186
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6558
31.2k
            } else if (!px.empty()) {
6559
                // Molodensky-Badekas case
6560
57
                auto uom_pivot =
6561
57
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6562
6563
57
                parameters.emplace_back(createOpParamNameEPSGCode(
6564
57
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6565
57
                values.emplace_back(createLength(px, uom_pivot));
6566
6567
57
                parameters.emplace_back(createOpParamNameEPSGCode(
6568
57
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6569
57
                values.emplace_back(createLength(py, uom_pivot));
6570
6571
57
                parameters.emplace_back(createOpParamNameEPSGCode(
6572
57
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6573
57
                values.emplace_back(createLength(pz, uom_pivot));
6574
57
            }
6575
6576
32.3k
            auto props = d->createPropertiesSearchUsages(
6577
32.3k
                type, code, name, deprecated, description);
6578
32.3k
            if (!operation_version.empty()) {
6579
31.3k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6580
31.3k
                          operation_version);
6581
31.3k
            }
6582
6583
32.3k
            auto propsMethod =
6584
32.3k
                util::PropertyMap()
6585
32.3k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6586
32.3k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6587
32.3k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6588
6589
32.3k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6590
32.3k
            if (!accuracy.empty() && accuracy != "999.0") {
6591
32.0k
                accuracies.emplace_back(
6592
32.0k
                    metadata::PositionalAccuracy::create(accuracy));
6593
32.0k
            }
6594
32.3k
            return operation::Transformation::create(
6595
32.3k
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6596
32.3k
                values, accuracies);
6597
6598
32.3k
        } catch (const std::exception &ex) {
6599
0
            throw buildFactoryException("transformation", d->authority(), code,
6600
0
                                        ex);
6601
0
        }
6602
32.3k
    }
6603
6604
140k
    if (type == "grid_transformation") {
6605
103k
        auto res = d->runWithCodeParam(
6606
103k
            "SELECT name, description, "
6607
103k
            "method_auth_name, method_code, method_name, "
6608
103k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6609
103k
            "target_crs_code, "
6610
103k
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6611
103k
            "grid_name, "
6612
103k
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6613
103k
            "grid2_name, "
6614
103k
            "param1_auth_name, param1_code, param1_name, param1_value, "
6615
103k
            "param1_uom_auth_name, param1_uom_code, "
6616
103k
            "param2_auth_name, param2_code, param2_name, param2_value, "
6617
103k
            "param2_uom_auth_name, param2_uom_code, "
6618
103k
            "interpolation_crs_auth_name, interpolation_crs_code, "
6619
103k
            "operation_version, deprecated FROM "
6620
103k
            "grid_transformation WHERE auth_name = ? AND code = ?",
6621
103k
            code);
6622
103k
        if (res.empty()) {
6623
            // shouldn't happen if foreign keys are OK
6624
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6625
0
                                               d->authority(), code);
6626
0
        }
6627
103k
        try {
6628
103k
            const auto &row = res.front();
6629
103k
            size_t idx = 0;
6630
103k
            const auto &name = row[idx++];
6631
103k
            const auto &description = row[idx++];
6632
103k
            const auto &method_auth_name = row[idx++];
6633
103k
            const auto &method_code = row[idx++];
6634
103k
            const auto &method_name = row[idx++];
6635
103k
            const auto &source_crs_auth_name = row[idx++];
6636
103k
            const auto &source_crs_code = row[idx++];
6637
103k
            const auto &target_crs_auth_name = row[idx++];
6638
103k
            const auto &target_crs_code = row[idx++];
6639
103k
            const auto &accuracy = row[idx++];
6640
103k
            const auto &grid_param_auth_name = row[idx++];
6641
103k
            const auto &grid_param_code = row[idx++];
6642
103k
            const auto &grid_param_name = row[idx++];
6643
103k
            const auto &grid_name = row[idx++];
6644
103k
            const auto &grid2_param_auth_name = row[idx++];
6645
103k
            const auto &grid2_param_code = row[idx++];
6646
103k
            const auto &grid2_param_name = row[idx++];
6647
103k
            const auto &grid2_name = row[idx++];
6648
103k
            std::vector<operation::OperationParameterNNPtr> parameters;
6649
103k
            std::vector<operation::ParameterValueNNPtr> values;
6650
6651
103k
            parameters.emplace_back(operation::OperationParameter::create(
6652
103k
                util::PropertyMap()
6653
103k
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6654
103k
                    .set(metadata::Identifier::CODESPACE_KEY,
6655
103k
                         grid_param_auth_name)
6656
103k
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6657
103k
            values.emplace_back(
6658
103k
                operation::ParameterValue::createFilename(grid_name));
6659
103k
            if (!grid2_name.empty()) {
6660
96.8k
                parameters.emplace_back(operation::OperationParameter::create(
6661
96.8k
                    util::PropertyMap()
6662
96.8k
                        .set(common::IdentifiedObject::NAME_KEY,
6663
96.8k
                             grid2_param_name)
6664
96.8k
                        .set(metadata::Identifier::CODESPACE_KEY,
6665
96.8k
                             grid2_param_auth_name)
6666
96.8k
                        .set(metadata::Identifier::CODE_KEY,
6667
96.8k
                             grid2_param_code)));
6668
96.8k
                values.emplace_back(
6669
96.8k
                    operation::ParameterValue::createFilename(grid2_name));
6670
96.8k
            }
6671
6672
103k
            const size_t base_param_idx = idx;
6673
103k
            constexpr size_t N_MAX_PARAMS_GRID_TRANSFORMATION = 2;
6674
104k
            for (size_t i = 0; i < N_MAX_PARAMS_GRID_TRANSFORMATION; ++i) {
6675
104k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6676
104k
                if (param_auth_name.empty()) {
6677
103k
                    break;
6678
103k
                }
6679
94
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6680
94
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6681
94
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6682
94
                const auto &param_uom_auth_name =
6683
94
                    row[base_param_idx + i * 6 + 4];
6684
94
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6685
94
                parameters.emplace_back(operation::OperationParameter::create(
6686
94
                    util::PropertyMap()
6687
94
                        .set(metadata::Identifier::CODESPACE_KEY,
6688
94
                             param_auth_name)
6689
94
                        .set(metadata::Identifier::CODE_KEY, param_code)
6690
94
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6691
94
                std::string normalized_uom_code(param_uom_code);
6692
94
                const double normalized_value = normalizeMeasure(
6693
94
                    param_uom_code, param_value, normalized_uom_code);
6694
94
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6695
94
                                                  normalized_uom_code);
6696
94
                values.emplace_back(operation::ParameterValue::create(
6697
94
                    common::Measure(normalized_value, uom)));
6698
94
            }
6699
103k
            idx = base_param_idx + 6 * N_MAX_PARAMS_GRID_TRANSFORMATION;
6700
6701
103k
            const auto &interpolation_crs_auth_name = row[idx++];
6702
103k
            const auto &interpolation_crs_code = row[idx++];
6703
103k
            const auto &operation_version = row[idx++];
6704
103k
            const auto &deprecated_str = row[idx++];
6705
103k
            const bool deprecated = deprecated_str == "1";
6706
103k
            assert(idx == row.size());
6707
6708
103k
            auto sourceCRS =
6709
103k
                d->createFactory(source_crs_auth_name)
6710
103k
                    ->createCoordinateReferenceSystem(source_crs_code);
6711
103k
            auto targetCRS =
6712
103k
                d->createFactory(target_crs_auth_name)
6713
103k
                    ->createCoordinateReferenceSystem(target_crs_code);
6714
103k
            auto interpolationCRS =
6715
103k
                interpolation_crs_auth_name.empty()
6716
103k
                    ? nullptr
6717
103k
                    : d->createFactory(interpolation_crs_auth_name)
6718
172
                          ->createCoordinateReferenceSystem(
6719
172
                              interpolation_crs_code)
6720
172
                          .as_nullable();
6721
6722
103k
            auto props = d->createPropertiesSearchUsages(
6723
103k
                type, code, name, deprecated, description);
6724
103k
            if (!operation_version.empty()) {
6725
103k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6726
103k
                          operation_version);
6727
103k
            }
6728
103k
            auto propsMethod =
6729
103k
                util::PropertyMap()
6730
103k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6731
103k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6732
103k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6733
6734
103k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6735
103k
            if (!accuracy.empty() && accuracy != "999.0") {
6736
103k
                accuracies.emplace_back(
6737
103k
                    metadata::PositionalAccuracy::create(accuracy));
6738
103k
            }
6739
6740
            // A bit fragile to detect the operation type with the method name,
6741
            // but not worth changing the database model
6742
103k
            if (starts_with(method_name, "Point motion")) {
6743
12
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6744
0
                    throw operation::InvalidOperation(
6745
0
                        "source_crs and target_crs should be the same for a "
6746
0
                        "PointMotionOperation");
6747
0
                }
6748
6749
12
                auto pmo = operation::PointMotionOperation::create(
6750
12
                    props, sourceCRS, propsMethod, parameters, values,
6751
12
                    accuracies);
6752
12
                if (usePROJAlternativeGridNames) {
6753
12
                    return pmo->substitutePROJAlternativeGridNames(
6754
12
                        d->context());
6755
12
                }
6756
0
                return pmo;
6757
12
            }
6758
6759
103k
            auto transf = operation::Transformation::create(
6760
103k
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6761
103k
                parameters, values, accuracies);
6762
103k
            if (usePROJAlternativeGridNames) {
6763
103k
                return transf->substitutePROJAlternativeGridNames(d->context());
6764
103k
            }
6765
0
            return transf;
6766
6767
103k
        } catch (const std::exception &ex) {
6768
0
            throw buildFactoryException("transformation", d->authority(), code,
6769
0
                                        ex);
6770
0
        }
6771
103k
    }
6772
6773
36.6k
    if (type == "other_transformation") {
6774
741
        std::ostringstream buffer;
6775
741
        buffer.imbue(std::locale::classic());
6776
741
        buffer
6777
741
            << "SELECT name, description, "
6778
741
               "method_auth_name, method_code, method_name, "
6779
741
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6780
741
               "target_crs_code, "
6781
741
               "grid_param_auth_name, grid_param_code, grid_param_name, "
6782
741
               "grid_name, "
6783
741
               "interpolation_crs_auth_name, interpolation_crs_code, "
6784
741
               "operation_version, accuracy, deprecated";
6785
741
        constexpr int N_MAX_PARAMS_OTHER_TRANSFORMATION = 9;
6786
7.41k
        for (size_t i = 1; i <= N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6787
6.66k
            buffer << ", param" << i << "_auth_name";
6788
6.66k
            buffer << ", param" << i << "_code";
6789
6.66k
            buffer << ", param" << i << "_name";
6790
6.66k
            buffer << ", param" << i << "_value";
6791
6.66k
            buffer << ", param" << i << "_uom_auth_name";
6792
6.66k
            buffer << ", param" << i << "_uom_code";
6793
6.66k
        }
6794
741
        buffer << " FROM other_transformation "
6795
741
                  "WHERE auth_name = ? AND code = ?";
6796
6797
741
        auto res = d->runWithCodeParam(buffer.str(), code);
6798
741
        if (res.empty()) {
6799
            // shouldn't happen if foreign keys are OK
6800
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6801
0
                                               d->authority(), code);
6802
0
        }
6803
741
        try {
6804
741
            const auto &row = res.front();
6805
741
            size_t idx = 0;
6806
741
            const auto &name = row[idx++];
6807
741
            const auto &description = row[idx++];
6808
741
            const auto &method_auth_name = row[idx++];
6809
741
            const auto &method_code = row[idx++];
6810
741
            const auto &method_name = row[idx++];
6811
741
            const auto &source_crs_auth_name = row[idx++];
6812
741
            const auto &source_crs_code = row[idx++];
6813
741
            const auto &target_crs_auth_name = row[idx++];
6814
741
            const auto &target_crs_code = row[idx++];
6815
741
            const auto &grid_param_auth_name = row[idx++];
6816
741
            const auto &grid_param_code = row[idx++];
6817
741
            const auto &grid_param_name = row[idx++];
6818
741
            const auto &grid_name = row[idx++];
6819
741
            const auto &interpolation_crs_auth_name = row[idx++];
6820
741
            const auto &interpolation_crs_code = row[idx++];
6821
741
            const auto &operation_version = row[idx++];
6822
741
            const auto &accuracy = row[idx++];
6823
741
            const auto &deprecated_str = row[idx++];
6824
741
            const bool deprecated = deprecated_str == "1";
6825
6826
741
            const size_t base_param_idx = idx;
6827
741
            std::vector<operation::OperationParameterNNPtr> parameters;
6828
741
            std::vector<operation::ParameterValueNNPtr> values;
6829
1.48k
            for (size_t i = 0; i < N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6830
1.42k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6831
1.42k
                if (param_auth_name.empty()) {
6832
683
                    break;
6833
683
                }
6834
741
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6835
741
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6836
741
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6837
741
                const auto &param_uom_auth_name =
6838
741
                    row[base_param_idx + i * 6 + 4];
6839
741
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6840
6841
741
                parameters.emplace_back(operation::OperationParameter::create(
6842
741
                    util::PropertyMap()
6843
741
                        .set(metadata::Identifier::CODESPACE_KEY,
6844
741
                             param_auth_name)
6845
741
                        .set(metadata::Identifier::CODE_KEY, param_code)
6846
741
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6847
741
                std::string normalized_uom_code(param_uom_code);
6848
741
                const double normalized_value = normalizeMeasure(
6849
741
                    param_uom_code, param_value, normalized_uom_code);
6850
741
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6851
741
                                                  normalized_uom_code);
6852
741
                values.emplace_back(operation::ParameterValue::create(
6853
741
                    common::Measure(normalized_value, uom)));
6854
741
            }
6855
741
            idx = base_param_idx + 6 * N_MAX_PARAMS_OTHER_TRANSFORMATION;
6856
741
            (void)idx;
6857
741
            assert(idx == row.size());
6858
6859
741
            if (!grid_name.empty()) {
6860
58
                parameters.emplace_back(operation::OperationParameter::create(
6861
58
                    util::PropertyMap()
6862
58
                        .set(common::IdentifiedObject::NAME_KEY,
6863
58
                             grid_param_name)
6864
58
                        .set(metadata::Identifier::CODESPACE_KEY,
6865
58
                             grid_param_auth_name)
6866
58
                        .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6867
58
                values.emplace_back(
6868
58
                    operation::ParameterValue::createFilename(grid_name));
6869
58
            }
6870
6871
741
            auto sourceCRS =
6872
741
                d->createFactory(source_crs_auth_name)
6873
741
                    ->createCoordinateReferenceSystem(source_crs_code);
6874
741
            auto targetCRS =
6875
741
                d->createFactory(target_crs_auth_name)
6876
741
                    ->createCoordinateReferenceSystem(target_crs_code);
6877
741
            auto interpolationCRS =
6878
741
                interpolation_crs_auth_name.empty()
6879
741
                    ? nullptr
6880
741
                    : d->createFactory(interpolation_crs_auth_name)
6881
62
                          ->createCoordinateReferenceSystem(
6882
62
                              interpolation_crs_code)
6883
62
                          .as_nullable();
6884
6885
741
            auto props = d->createPropertiesSearchUsages(
6886
741
                type, code, name, deprecated, description);
6887
741
            if (!operation_version.empty()) {
6888
680
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6889
680
                          operation_version);
6890
680
            }
6891
6892
741
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6893
741
            if (!accuracy.empty() && accuracy != "999.0") {
6894
740
                accuracies.emplace_back(
6895
740
                    metadata::PositionalAccuracy::create(accuracy));
6896
740
            }
6897
6898
741
            if (method_auth_name == "PROJ") {
6899
515
                if (method_code == "PROJString") {
6900
515
                    auto op = operation::SingleOperation::createPROJBased(
6901
515
                        props, method_name, sourceCRS, targetCRS, accuracies);
6902
515
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6903
515
                    return op;
6904
515
                } else if (method_code == "WKT") {
6905
0
                    auto op = util::nn_dynamic_pointer_cast<
6906
0
                        operation::CoordinateOperation>(
6907
0
                        WKTParser().createFromWKT(method_name));
6908
0
                    if (!op) {
6909
0
                        throw FactoryException("WKT string does not express a "
6910
0
                                               "coordinate operation");
6911
0
                    }
6912
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6913
0
                    return NN_NO_CHECK(op);
6914
0
                }
6915
515
            }
6916
6917
226
            auto propsMethod =
6918
226
                util::PropertyMap()
6919
226
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6920
226
                    .set(metadata::Identifier::CODE_KEY, method_code)
6921
226
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6922
6923
226
            if (method_auth_name == metadata::Identifier::EPSG) {
6924
226
                int method_code_int = std::atoi(method_code.c_str());
6925
226
                if (operation::isAxisOrderReversal(method_code_int) ||
6926
191
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6927
191
                    method_code_int ==
6928
191
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6929
191
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6930
36
                    auto op = operation::Conversion::create(props, propsMethod,
6931
36
                                                            parameters, values);
6932
36
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6933
36
                    return op;
6934
36
                }
6935
226
            }
6936
190
            auto transf = operation::Transformation::create(
6937
190
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6938
190
                parameters, values, accuracies);
6939
190
            if (usePROJAlternativeGridNames) {
6940
190
                return transf->substitutePROJAlternativeGridNames(d->context());
6941
190
            }
6942
0
            return transf;
6943
6944
190
        } catch (const std::exception &ex) {
6945
0
            throw buildFactoryException("transformation", d->authority(), code,
6946
0
                                        ex);
6947
0
        }
6948
741
    }
6949
6950
35.8k
    if (allowConcatenated && type == "concatenated_operation") {
6951
35.8k
        auto res = d->runWithCodeParam(
6952
35.8k
            "SELECT name, description, "
6953
35.8k
            "source_crs_auth_name, source_crs_code, "
6954
35.8k
            "target_crs_auth_name, target_crs_code, "
6955
35.8k
            "accuracy, "
6956
35.8k
            "operation_version, deprecated FROM "
6957
35.8k
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6958
35.8k
            code);
6959
35.8k
        if (res.empty()) {
6960
            // shouldn't happen if foreign keys are OK
6961
0
            throw NoSuchAuthorityCodeException(
6962
0
                "concatenated_operation not found", d->authority(), code);
6963
0
        }
6964
6965
35.8k
        auto resSteps = d->runWithCodeParam(
6966
35.8k
            "SELECT step_auth_name, step_code, step_direction FROM "
6967
35.8k
            "concatenated_operation_step WHERE operation_auth_name = ? "
6968
35.8k
            "AND operation_code = ? ORDER BY step_number",
6969
35.8k
            code);
6970
6971
35.8k
        try {
6972
35.8k
            const auto &row = res.front();
6973
35.8k
            size_t idx = 0;
6974
35.8k
            const auto &name = row[idx++];
6975
35.8k
            const auto &description = row[idx++];
6976
35.8k
            const auto &source_crs_auth_name = row[idx++];
6977
35.8k
            const auto &source_crs_code = row[idx++];
6978
35.8k
            const auto &target_crs_auth_name = row[idx++];
6979
35.8k
            const auto &target_crs_code = row[idx++];
6980
35.8k
            const auto &accuracy = row[idx++];
6981
35.8k
            const auto &operation_version = row[idx++];
6982
35.8k
            const auto &deprecated_str = row[idx++];
6983
35.8k
            const bool deprecated = deprecated_str == "1";
6984
6985
35.8k
            std::vector<operation::CoordinateOperationNNPtr> operations;
6986
35.8k
            size_t countExplicitDirection = 0;
6987
72.9k
            for (const auto &rowStep : resSteps) {
6988
72.9k
                const auto &step_auth_name = rowStep[0];
6989
72.9k
                const auto &step_code = rowStep[1];
6990
72.9k
                const auto &step_direction = rowStep[2];
6991
72.9k
                auto stepOp =
6992
72.9k
                    d->createFactory(step_auth_name)
6993
72.9k
                        ->createCoordinateOperation(step_code, false,
6994
72.9k
                                                    usePROJAlternativeGridNames,
6995
72.9k
                                                    std::string());
6996
72.9k
                if (step_direction == "forward") {
6997
3.71k
                    ++countExplicitDirection;
6998
3.71k
                    operations.push_back(std::move(stepOp));
6999
69.2k
                } else if (step_direction == "reverse") {
7000
109
                    ++countExplicitDirection;
7001
109
                    operations.push_back(stepOp->inverse());
7002
69.1k
                } else {
7003
69.1k
                    operations.push_back(std::move(stepOp));
7004
69.1k
                }
7005
72.9k
            }
7006
7007
35.8k
            if (countExplicitDirection > 0 &&
7008
1.63k
                countExplicitDirection != resSteps.size()) {
7009
0
                throw FactoryException("not all steps have a defined direction "
7010
0
                                       "for concatenated operation " +
7011
0
                                       code);
7012
0
            }
7013
7014
35.8k
            const bool fixDirectionAllowed = (countExplicitDirection == 0);
7015
35.8k
            operation::ConcatenatedOperation::fixSteps(
7016
35.8k
                d->createFactory(source_crs_auth_name)
7017
35.8k
                    ->createCoordinateReferenceSystem(source_crs_code),
7018
35.8k
                d->createFactory(target_crs_auth_name)
7019
35.8k
                    ->createCoordinateReferenceSystem(target_crs_code),
7020
35.8k
                operations, d->context(), fixDirectionAllowed);
7021
7022
35.8k
            auto props = d->createPropertiesSearchUsages(
7023
35.8k
                type, code, name, deprecated, description);
7024
35.8k
            if (!operation_version.empty()) {
7025
34.4k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
7026
34.4k
                          operation_version);
7027
34.4k
            }
7028
7029
35.8k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
7030
35.8k
            if (!accuracy.empty()) {
7031
35.8k
                if (accuracy != "999.0") {
7032
35.8k
                    accuracies.emplace_back(
7033
35.8k
                        metadata::PositionalAccuracy::create(accuracy));
7034
35.8k
                }
7035
35.8k
            } else {
7036
                // Try to compute a reasonable accuracy from the members
7037
13
                double totalAcc = -1;
7038
13
                try {
7039
35
                    for (const auto &op : operations) {
7040
35
                        auto accs = op->coordinateOperationAccuracies();
7041
35
                        if (accs.size() == 1) {
7042
15
                            double acc = c_locale_stod(accs[0]->value());
7043
15
                            if (totalAcc < 0) {
7044
6
                                totalAcc = acc;
7045
9
                            } else {
7046
9
                                totalAcc += acc;
7047
9
                            }
7048
20
                        } else if (dynamic_cast<const operation::Conversion *>(
7049
20
                                       op.get())) {
7050
                            // A conversion is perfectly accurate.
7051
16
                            if (totalAcc < 0) {
7052
7
                                totalAcc = 0;
7053
7
                            }
7054
16
                        } else {
7055
4
                            totalAcc = -1;
7056
4
                            break;
7057
4
                        }
7058
35
                    }
7059
13
                    if (totalAcc >= 0) {
7060
9
                        accuracies.emplace_back(
7061
9
                            metadata::PositionalAccuracy::create(
7062
9
                                toString(totalAcc)));
7063
9
                    }
7064
13
                } catch (const std::exception &) {
7065
0
                }
7066
13
            }
7067
35.8k
            return operation::ConcatenatedOperation::create(props, operations,
7068
35.8k
                                                            accuracies);
7069
7070
35.8k
        } catch (const std::exception &ex) {
7071
0
            throw buildFactoryException("transformation", d->authority(), code,
7072
0
                                        ex);
7073
0
        }
7074
35.8k
    }
7075
7076
0
    throw FactoryException("unhandled coordinate operation type: " + type);
7077
35.8k
}
7078
7079
// ---------------------------------------------------------------------------
7080
7081
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7082
 *
7083
 * The list is ordered with preferred operations first. No attempt is made
7084
 * at inferring operations that are not explicitly in the database.
7085
 *
7086
 * Deprecated operations are rejected.
7087
 *
7088
 * @param sourceCRSCode Source CRS code allocated by authority.
7089
 * @param targetCRSCode Source CRS code allocated by authority.
7090
 * @return list of coordinate operations
7091
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7092
 * @throw FactoryException in case of other errors.
7093
 */
7094
7095
std::vector<operation::CoordinateOperationNNPtr>
7096
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7097
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
7098
0
    return createFromCoordinateReferenceSystemCodes(
7099
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
7100
0
        false, false, false);
7101
0
}
7102
7103
// ---------------------------------------------------------------------------
7104
7105
/** \brief Returns a list of geoid models available for that crs
7106
 *
7107
 * The list includes the geoid models connected directly with the crs,
7108
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
7109
 *
7110
 * @param code crs code allocated by authority.
7111
 * @return list of geoid model names
7112
 * @throw FactoryException in case of error.
7113
 */
7114
7115
std::list<std::string>
7116
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
7117
7118
0
    ListOfParams params;
7119
0
    std::string sql;
7120
0
    sql += "SELECT DISTINCT GM0.name "
7121
0
           "  FROM geoid_model GM0 "
7122
0
           "INNER JOIN grid_transformation GT0 "
7123
0
           "  ON  GT0.code = GM0.operation_code "
7124
0
           "  AND GT0.auth_name = GM0.operation_auth_name "
7125
0
           "  AND GT0.deprecated = 0 "
7126
0
           "INNER JOIN vertical_crs VC0 "
7127
0
           "  ON VC0.code = GT0.target_crs_code "
7128
0
           "  AND VC0.auth_name = GT0.target_crs_auth_name "
7129
0
           "INNER JOIN vertical_crs VC1 "
7130
0
           "  ON VC1.datum_code = VC0.datum_code "
7131
0
           "  AND VC1.datum_auth_name = VC0.datum_auth_name "
7132
0
           "  AND VC1.code = ? ";
7133
0
    params.emplace_back(code);
7134
0
    if (d->hasAuthorityRestriction()) {
7135
0
        sql += " AND GT0.target_crs_auth_name = ? ";
7136
0
        params.emplace_back(d->authority());
7137
0
    }
7138
0
    sql += " ORDER BY 1 ";
7139
7140
0
    auto sqlRes = d->run(sql, params);
7141
0
    std::list<std::string> res;
7142
0
    for (const auto &row : sqlRes) {
7143
0
        res.push_back(row[0]);
7144
0
    }
7145
0
    return res;
7146
0
}
7147
7148
// ---------------------------------------------------------------------------
7149
7150
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7151
 *
7152
 * The list is ordered with preferred operations first. No attempt is made
7153
 * at inferring operations that are not explicitly in the database (see
7154
 * createFromCRSCodesWithIntermediates() for that), and only
7155
 * source -> target operations are searched (i.e. if target -> source is
7156
 * present, you need to call this method with the arguments reversed, and apply
7157
 * the reverse transformations).
7158
 *
7159
 * Deprecated operations are rejected.
7160
 *
7161
 * If getAuthority() returns empty, then coordinate operations from all
7162
 * authorities are considered.
7163
 *
7164
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7165
 * @param sourceCRSCode Source CRS code allocated by authority
7166
 * sourceCRSAuthName.
7167
 * @param targetCRSAuthName Authority name of targetCRSCode
7168
 * @param targetCRSCode Source CRS code allocated by authority
7169
 * targetCRSAuthName.
7170
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7171
 * should be substituted to the official grid names.
7172
 * @param discardIfMissingGrid Whether coordinate operations that reference
7173
 * missing grids should be removed from the result set.
7174
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7175
 * as available (typically when network is enabled).
7176
 * @param discardSuperseded Whether coordinate operations that are superseded
7177
 * (but not deprecated) should be removed from the result set.
7178
 * @param tryReverseOrder whether to search in the reverse order too (and thus
7179
 * inverse results found that way)
7180
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
7181
 * intersectingExtent2 should be honored in a strict way.
7182
 * @param intersectingExtent1 Optional extent that the resulting operations
7183
 * must intersect.
7184
 * @param intersectingExtent2 Optional extent that the resulting operations
7185
 * must intersect.
7186
 * @return list of coordinate operations
7187
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7188
 * @throw FactoryException in case of other errors.
7189
 */
7190
7191
std::vector<operation::CoordinateOperationNNPtr>
7192
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7193
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7194
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7195
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7196
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7197
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
7198
    const metadata::ExtentPtr &intersectingExtent1,
7199
185k
    const metadata::ExtentPtr &intersectingExtent2) const {
7200
7201
185k
    auto cacheKey(d->authority());
7202
185k
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
7203
185k
    cacheKey += sourceCRSCode;
7204
185k
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
7205
185k
    cacheKey += targetCRSCode;
7206
185k
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
7207
185k
    cacheKey += (discardIfMissingGrid ? '1' : '0');
7208
185k
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
7209
185k
    cacheKey += (discardSuperseded ? '1' : '0');
7210
185k
    cacheKey += (tryReverseOrder ? '1' : '0');
7211
185k
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
7212
371k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7213
371k
        if (extent) {
7214
60.0k
            const auto &geogExtent = extent->geographicElements();
7215
60.0k
            if (geogExtent.size() == 1) {
7216
60.0k
                auto bbox =
7217
60.0k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7218
60.0k
                        geogExtent[0].get());
7219
60.0k
                if (bbox) {
7220
60.0k
                    cacheKey += toString(bbox->southBoundLatitude());
7221
60.0k
                    cacheKey += toString(bbox->westBoundLongitude());
7222
60.0k
                    cacheKey += toString(bbox->northBoundLatitude());
7223
60.0k
                    cacheKey += toString(bbox->eastBoundLongitude());
7224
60.0k
                }
7225
60.0k
            }
7226
60.0k
        }
7227
371k
    }
7228
7229
185k
    std::vector<operation::CoordinateOperationNNPtr> list;
7230
7231
185k
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
7232
145k
        return list;
7233
145k
    }
7234
7235
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
7236
    // In which case use the conversion of the ProjectedCRS
7237
39.8k
    if (!targetCRSAuthName.empty()) {
7238
39.8k
        auto targetFactory = d->createFactory(targetCRSAuthName);
7239
39.8k
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
7240
39.8k
                                        targetCRSCode);
7241
39.8k
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
7242
39.8k
            cacheKeyProjectedCRS);
7243
39.8k
        crs::ProjectedCRSPtr targetProjCRS;
7244
39.8k
        if (crs) {
7245
39.8k
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
7246
39.8k
        } else {
7247
0
            const auto sqlRes =
7248
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
7249
0
            if (!sqlRes.empty()) {
7250
0
                try {
7251
0
                    targetProjCRS =
7252
0
                        targetFactory->d
7253
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
7254
0
                            .as_nullable();
7255
0
                } catch (const std::exception &) {
7256
0
                }
7257
0
            }
7258
0
        }
7259
39.8k
        if (targetProjCRS) {
7260
3
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
7261
3
            if (sourceCRSAuthName.empty() ||
7262
3
                (!baseIds.empty() &&
7263
3
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
7264
3
                 baseIds.front()->code() == sourceCRSCode)) {
7265
0
                bool ok = true;
7266
0
                auto conv = targetProjCRS->derivingConversion();
7267
0
                if (d->hasAuthorityRestriction()) {
7268
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
7269
0
                         d->authority();
7270
0
                }
7271
0
                if (ok) {
7272
0
                    list.emplace_back(conv);
7273
0
                    d->context()->d->cache(cacheKey, list);
7274
0
                    return list;
7275
0
                }
7276
0
            }
7277
3
        }
7278
39.8k
    }
7279
7280
39.8k
    std::string sql;
7281
39.8k
    if (discardSuperseded) {
7282
39.8k
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
7283
39.8k
              "cov.target_crs_auth_name, cov.target_crs_code, "
7284
39.8k
              "cov.auth_name, cov.code, cov.table_name, "
7285
39.8k
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7286
39.8k
              "extent.east_lon, "
7287
39.8k
              "ss.replacement_auth_name, ss.replacement_code, "
7288
39.8k
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
7289
39.8k
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
7290
39.8k
              "FROM "
7291
39.8k
              "coordinate_operation_view cov "
7292
39.8k
              "JOIN usage ON "
7293
39.8k
              "usage.object_table_name = cov.table_name AND "
7294
39.8k
              "usage.object_auth_name = cov.auth_name AND "
7295
39.8k
              "usage.object_code = cov.code "
7296
39.8k
              "JOIN extent "
7297
39.8k
              "ON extent.auth_name = usage.extent_auth_name AND "
7298
39.8k
              "extent.code = usage.extent_code "
7299
39.8k
              "LEFT JOIN supersession ss ON "
7300
39.8k
              "ss.superseded_table_name = cov.table_name AND "
7301
39.8k
              "ss.superseded_auth_name = cov.auth_name AND "
7302
39.8k
              "ss.superseded_code = cov.code AND "
7303
39.8k
              "ss.superseded_table_name = ss.replacement_table_name AND "
7304
39.8k
              "ss.same_source_target_crs = 1 "
7305
39.8k
              "LEFT JOIN grid_transformation gt ON "
7306
39.8k
              "gt.auth_name = ss.replacement_auth_name AND "
7307
39.8k
              "gt.code = ss.replacement_code "
7308
39.8k
              "LEFT JOIN grid_alternatives ga ON "
7309
39.8k
              "ga.original_grid_name = gt.grid_name "
7310
39.8k
              "WHERE ";
7311
39.8k
    } else {
7312
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
7313
0
              "target_crs_auth_name, target_crs_code, "
7314
0
              "cov.auth_name, cov.code, cov.table_name, "
7315
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7316
0
              "extent.east_lon "
7317
0
              "FROM "
7318
0
              "coordinate_operation_view cov "
7319
0
              "JOIN usage ON "
7320
0
              "usage.object_table_name = cov.table_name AND "
7321
0
              "usage.object_auth_name = cov.auth_name AND "
7322
0
              "usage.object_code = cov.code "
7323
0
              "JOIN extent "
7324
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7325
0
              "extent.code = usage.extent_code "
7326
0
              "WHERE ";
7327
0
    }
7328
39.8k
    ListOfParams params;
7329
39.8k
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
7330
39.8k
        if (tryReverseOrder) {
7331
39.8k
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7332
39.8k
                   "AND "
7333
39.8k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
7334
39.8k
                   "OR "
7335
39.8k
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7336
39.8k
                   "AND "
7337
39.8k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
7338
39.8k
                   "AND ";
7339
39.8k
            params.emplace_back(sourceCRSAuthName);
7340
39.8k
            params.emplace_back(sourceCRSCode);
7341
39.8k
            params.emplace_back(targetCRSAuthName);
7342
39.8k
            params.emplace_back(targetCRSCode);
7343
39.8k
            params.emplace_back(targetCRSAuthName);
7344
39.8k
            params.emplace_back(targetCRSCode);
7345
39.8k
            params.emplace_back(sourceCRSAuthName);
7346
39.8k
            params.emplace_back(sourceCRSCode);
7347
39.8k
        } else {
7348
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7349
0
                   "AND "
7350
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7351
0
                   "AND ";
7352
0
            params.emplace_back(sourceCRSAuthName);
7353
0
            params.emplace_back(sourceCRSCode);
7354
0
            params.emplace_back(targetCRSAuthName);
7355
0
            params.emplace_back(targetCRSCode);
7356
0
        }
7357
39.8k
    } else if (!sourceCRSAuthName.empty()) {
7358
0
        if (tryReverseOrder) {
7359
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7360
0
                   ")OR "
7361
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7362
0
                   " AND ";
7363
0
            params.emplace_back(sourceCRSAuthName);
7364
0
            params.emplace_back(sourceCRSCode);
7365
0
            params.emplace_back(sourceCRSAuthName);
7366
0
            params.emplace_back(sourceCRSCode);
7367
0
        } else {
7368
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7369
0
                   "AND ";
7370
0
            params.emplace_back(sourceCRSAuthName);
7371
0
            params.emplace_back(sourceCRSCode);
7372
0
        }
7373
24
    } else if (!targetCRSAuthName.empty()) {
7374
24
        if (tryReverseOrder) {
7375
24
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
7376
24
                   " OR "
7377
24
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7378
24
                   " AND ";
7379
24
            params.emplace_back(targetCRSAuthName);
7380
24
            params.emplace_back(targetCRSCode);
7381
24
            params.emplace_back(targetCRSAuthName);
7382
24
            params.emplace_back(targetCRSCode);
7383
24
        } else {
7384
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7385
0
                   "AND ";
7386
0
            params.emplace_back(targetCRSAuthName);
7387
0
            params.emplace_back(targetCRSCode);
7388
0
        }
7389
24
    }
7390
39.8k
    sql += "cov.deprecated = 0";
7391
39.8k
    if (d->hasAuthorityRestriction()) {
7392
38.6k
        sql += " AND cov.auth_name = ?";
7393
38.6k
        params.emplace_back(d->authority());
7394
38.6k
    }
7395
39.8k
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
7396
39.8k
           "east_lon) DESC, "
7397
39.8k
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
7398
39.8k
    auto res = d->run(sql, params);
7399
39.8k
    std::set<std::pair<std::string, std::string>> setTransf;
7400
39.8k
    if (discardSuperseded) {
7401
53.0k
        for (const auto &row : res) {
7402
53.0k
            const auto &auth_name = row[4];
7403
53.0k
            const auto &code = row[5];
7404
53.0k
            setTransf.insert(
7405
53.0k
                std::pair<std::string, std::string>(auth_name, code));
7406
53.0k
        }
7407
39.8k
    }
7408
7409
    // Do a pass to determine if there are transformations that intersect
7410
    // intersectingExtent1 & intersectingExtent2
7411
39.8k
    std::vector<bool> intersectingTransformations;
7412
39.8k
    intersectingTransformations.resize(res.size());
7413
39.8k
    bool hasIntersectingTransformations = false;
7414
39.8k
    size_t i = 0;
7415
53.0k
    for (const auto &row : res) {
7416
53.0k
        size_t thisI = i;
7417
53.0k
        ++i;
7418
53.0k
        if (discardSuperseded) {
7419
53.0k
            const auto &replacement_auth_name = row[11];
7420
53.0k
            const auto &replacement_code = row[12];
7421
53.0k
            const bool replacement_is_grid_transform = row[13] == "1";
7422
53.0k
            const bool replacement_is_known_grid = row[14] == "1";
7423
53.0k
            if (!replacement_auth_name.empty() &&
7424
                // Ignore supersession if the replacement uses a unknown grid
7425
348
                !(replacement_is_grid_transform &&
7426
270
                  !replacement_is_known_grid) &&
7427
348
                setTransf.find(std::pair<std::string, std::string>(
7428
348
                    replacement_auth_name, replacement_code)) !=
7429
348
                    setTransf.end()) {
7430
                // Skip transformations that are superseded by others that got
7431
                // returned in the result set.
7432
348
                continue;
7433
348
            }
7434
53.0k
        }
7435
7436
52.7k
        bool intersecting = true;
7437
52.7k
        try {
7438
52.7k
            double south_lat = c_locale_stod(row[7]);
7439
52.7k
            double west_lon = c_locale_stod(row[8]);
7440
52.7k
            double north_lat = c_locale_stod(row[9]);
7441
52.7k
            double east_lon = c_locale_stod(row[10]);
7442
52.7k
            auto transf_extent = metadata::Extent::createFromBBOX(
7443
52.7k
                west_lon, south_lat, east_lon, north_lat);
7444
7445
52.7k
            for (const auto &extent :
7446
105k
                 {intersectingExtent1, intersectingExtent2}) {
7447
105k
                if (extent) {
7448
1.35k
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
7449
8
                        intersecting = false;
7450
8
                        break;
7451
8
                    }
7452
1.35k
                }
7453
105k
            }
7454
52.7k
        } catch (const std::exception &) {
7455
0
        }
7456
7457
52.7k
        intersectingTransformations[thisI] = intersecting;
7458
52.7k
        if (intersecting)
7459
52.7k
            hasIntersectingTransformations = true;
7460
52.7k
    }
7461
7462
    // If there are intersecting transformations, then only report those ones
7463
    // If there are no intersecting transformations, report all of them
7464
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
7465
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
7466
    // coordinates are used
7467
39.8k
    i = 0;
7468
53.0k
    for (const auto &row : res) {
7469
53.0k
        size_t thisI = i;
7470
53.0k
        ++i;
7471
53.0k
        if ((hasIntersectingTransformations ||
7472
49
             reportOnlyIntersectingTransformations) &&
7473
53.0k
            !intersectingTransformations[thisI]) {
7474
356
            continue;
7475
356
        }
7476
52.7k
        if (discardSuperseded) {
7477
52.7k
            const auto &replacement_auth_name = row[11];
7478
52.7k
            const auto &replacement_code = row[12];
7479
52.7k
            const bool replacement_is_grid_transform = row[13] == "1";
7480
52.7k
            const bool replacement_is_known_grid = row[14] == "1";
7481
52.7k
            if (!replacement_auth_name.empty() &&
7482
                // Ignore supersession if the replacement uses a unknown grid
7483
0
                !(replacement_is_grid_transform &&
7484
0
                  !replacement_is_known_grid) &&
7485
0
                setTransf.find(std::pair<std::string, std::string>(
7486
0
                    replacement_auth_name, replacement_code)) !=
7487
0
                    setTransf.end()) {
7488
                // Skip transformations that are superseded by others that got
7489
                // returned in the result set.
7490
0
                continue;
7491
0
            }
7492
52.7k
        }
7493
7494
52.7k
        const auto &source_crs_auth_name = row[0];
7495
52.7k
        const auto &source_crs_code = row[1];
7496
52.7k
        const auto &target_crs_auth_name = row[2];
7497
52.7k
        const auto &target_crs_code = row[3];
7498
52.7k
        const auto &auth_name = row[4];
7499
52.7k
        const auto &code = row[5];
7500
52.7k
        const auto &table_name = row[6];
7501
52.7k
        try {
7502
52.7k
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
7503
52.7k
                code, true, usePROJAlternativeGridNames, table_name);
7504
52.7k
            if (tryReverseOrder &&
7505
52.7k
                (!sourceCRSAuthName.empty()
7506
52.7k
                     ? (source_crs_auth_name != sourceCRSAuthName ||
7507
52.6k
                        source_crs_code != sourceCRSCode)
7508
52.7k
                     : (target_crs_auth_name != targetCRSAuthName ||
7509
33.4k
                        target_crs_code != targetCRSCode))) {
7510
33.4k
                op = op->inverse();
7511
33.4k
            }
7512
52.7k
            if (!discardIfMissingGrid ||
7513
52.7k
                !d->rejectOpDueToMissingGrid(op,
7514
52.7k
                                             considerKnownGridsAsAvailable)) {
7515
18.1k
                list.emplace_back(op);
7516
18.1k
            }
7517
52.7k
        } catch (const std::exception &e) {
7518
            // Mostly for debugging purposes when using an inconsistent
7519
            // database
7520
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7521
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7522
0
            } else {
7523
0
                throw;
7524
0
            }
7525
0
        }
7526
52.7k
    }
7527
39.8k
    d->context()->d->cache(cacheKey, list);
7528
39.8k
    return list;
7529
39.8k
}
7530
7531
// ---------------------------------------------------------------------------
7532
7533
//! @cond Doxygen_Suppress
7534
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
7535
                               const std::string &sourceCRSAuthName,
7536
                               const std::string &sourceCRSCode,
7537
                               const std::string &targetCRSAuthName,
7538
46.2k
                               const std::string &targetCRSCode) {
7539
46.2k
    auto concat =
7540
46.2k
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
7541
46.2k
    if (!concat) {
7542
30.4k
        return false;
7543
30.4k
    }
7544
15.7k
    auto ops = concat->operations();
7545
16.5k
    for (size_t i = 0; i + 1 < ops.size(); i++) {
7546
16.1k
        auto targetCRS = ops[i]->targetCRS();
7547
16.1k
        if (targetCRS) {
7548
16.1k
            const auto &ids = targetCRS->identifiers();
7549
16.1k
            if (ids.size() == 1 &&
7550
16.1k
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
7551
16.0k
                  ids[0]->code() == sourceCRSCode) ||
7552
6.54k
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7553
15.2k
                  ids[0]->code() == targetCRSCode))) {
7554
15.2k
                return true;
7555
15.2k
            }
7556
16.1k
        }
7557
16.1k
    }
7558
461
    return false;
7559
15.7k
}
7560
//! @endcond
7561
7562
// ---------------------------------------------------------------------------
7563
7564
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7565
 * using intermediate codes.
7566
 *
7567
 * The list is ordered with preferred operations first.
7568
 *
7569
 * Deprecated operations are rejected.
7570
 *
7571
 * The method will take care of considering all potential combinations (i.e.
7572
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7573
 * call it with sourceCRS and targetCRS switched)
7574
 *
7575
 * If getAuthority() returns empty, then coordinate operations from all
7576
 * authorities are considered.
7577
 *
7578
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7579
 * @param sourceCRSCode Source CRS code allocated by authority
7580
 * sourceCRSAuthName.
7581
 * @param targetCRSAuthName Authority name of targetCRSCode
7582
 * @param targetCRSCode Source CRS code allocated by authority
7583
 * targetCRSAuthName.
7584
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7585
 * should be substituted to the official grid names.
7586
 * @param discardIfMissingGrid Whether coordinate operations that reference
7587
 * missing grids should be removed from the result set.
7588
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7589
 * as available (typically when network is enabled).
7590
 * @param discardSuperseded Whether coordinate operations that are superseded
7591
 * (but not deprecated) should be removed from the result set.
7592
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7593
 * used as potential intermediate CRS. If the list is empty, the database will
7594
 * be used to find common CRS in operations involving both the source and
7595
 * target CRS.
7596
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7597
 * object considered.
7598
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7599
 * @param allowedAuthorities One or several authority name allowed for the two
7600
 * coordinate operations that are going to be searched. When this vector is
7601
 * no empty, it overrides the authority of this object. This is useful for
7602
 * example when the coordinate operations to chain belong to two different
7603
 * allowed authorities.
7604
 * @param intersectingExtent1 Optional extent that the resulting operations
7605
 * must intersect.
7606
 * @param intersectingExtent2 Optional extent that the resulting operations
7607
 * must intersect.
7608
 * @param skipIntermediateExtentIntersection When true, skip the requirement
7609
 * that the extents of the two intermediate operations must intersect each
7610
 * other. This is useful when SourceTargetCRSExtentUse::NONE is set.
7611
 * @return list of coordinate operations
7612
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7613
 * @throw FactoryException in case of other errors.
7614
 */
7615
7616
std::vector<operation::CoordinateOperationNNPtr>
7617
AuthorityFactory::createFromCRSCodesWithIntermediates(
7618
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7619
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7620
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7621
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7622
    const std::vector<std::pair<std::string, std::string>>
7623
        &intermediateCRSAuthCodes,
7624
    ObjectType allowedIntermediateObjectType,
7625
    const std::vector<std::string> &allowedAuthorities,
7626
    const metadata::ExtentPtr &intersectingExtent1,
7627
    const metadata::ExtentPtr &intersectingExtent2,
7628
12.6k
    bool skipIntermediateExtentIntersection) const {
7629
7630
12.6k
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7631
7632
12.6k
    if (sourceCRSAuthName == targetCRSAuthName &&
7633
3.74k
        sourceCRSCode == targetCRSCode) {
7634
27
        return listTmp;
7635
27
    }
7636
7637
12.6k
    const auto CheckIfHasOperations = [this](const std::string &auth_name,
7638
23.2k
                                             const std::string &code) {
7639
23.2k
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7640
23.2k
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7641
23.2k
                        "(target_crs_auth_name = ? AND target_crs_code = ?) "
7642
23.2k
                        "LIMIT 1",
7643
23.2k
                        {auth_name, code, auth_name, code})
7644
23.2k
                     .empty());
7645
23.2k
    };
7646
7647
    // If the source or target CRS are not the source or target of an operation,
7648
    // do not run the next costly requests.
7649
12.6k
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7650
10.6k
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7651
3.86k
        return listTmp;
7652
3.86k
    }
7653
7654
8.77k
    const std::string sqlProlog(
7655
8.77k
        discardSuperseded
7656
8.77k
            ?
7657
7658
8.77k
            "SELECT v1.table_name as table1, "
7659
8.77k
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7660
8.77k
            "v1.accuracy AS accuracy1, "
7661
8.77k
            "v2.table_name as table2, "
7662
8.77k
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7663
8.77k
            "v2.accuracy as accuracy2, "
7664
8.77k
            "a1.south_lat AS south_lat1, "
7665
8.77k
            "a1.west_lon AS west_lon1, "
7666
8.77k
            "a1.north_lat AS north_lat1, "
7667
8.77k
            "a1.east_lon AS east_lon1, "
7668
8.77k
            "a2.south_lat AS south_lat2, "
7669
8.77k
            "a2.west_lon AS west_lon2, "
7670
8.77k
            "a2.north_lat AS north_lat2, "
7671
8.77k
            "a2.east_lon AS east_lon2, "
7672
8.77k
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7673
8.77k
            "ss1.replacement_code AS replacement_code1, "
7674
8.77k
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7675
8.77k
            "ss2.replacement_code AS replacement_code2 "
7676
8.77k
            "FROM coordinate_operation_view v1 "
7677
8.77k
            "JOIN coordinate_operation_view v2 "
7678
8.77k
            :
7679
7680
8.77k
            "SELECT v1.table_name as table1, "
7681
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7682
0
            "v1.accuracy AS accuracy1, "
7683
0
            "v2.table_name as table2, "
7684
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7685
0
            "v2.accuracy as accuracy2, "
7686
0
            "a1.south_lat AS south_lat1, "
7687
0
            "a1.west_lon AS west_lon1, "
7688
0
            "a1.north_lat AS north_lat1, "
7689
0
            "a1.east_lon AS east_lon1, "
7690
0
            "a2.south_lat AS south_lat2, "
7691
0
            "a2.west_lon AS west_lon2, "
7692
0
            "a2.north_lat AS north_lat2, "
7693
0
            "a2.east_lon AS east_lon2 "
7694
0
            "FROM coordinate_operation_view v1 "
7695
0
            "JOIN coordinate_operation_view v2 ");
7696
7697
8.77k
    const char *joinSupersession =
7698
8.77k
        "LEFT JOIN supersession ss1 ON "
7699
8.77k
        "ss1.superseded_table_name = v1.table_name AND "
7700
8.77k
        "ss1.superseded_auth_name = v1.auth_name AND "
7701
8.77k
        "ss1.superseded_code = v1.code AND "
7702
8.77k
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7703
8.77k
        "ss1.same_source_target_crs = 1 "
7704
8.77k
        "LEFT JOIN supersession ss2 ON "
7705
8.77k
        "ss2.superseded_table_name = v2.table_name AND "
7706
8.77k
        "ss2.superseded_auth_name = v2.auth_name AND "
7707
8.77k
        "ss2.superseded_code = v2.code AND "
7708
8.77k
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7709
8.77k
        "ss2.same_source_target_crs = 1 ";
7710
8.77k
    const std::string joinArea(
7711
8.77k
        (discardSuperseded ? std::string(joinSupersession) : std::string())
7712
8.77k
            .append("JOIN usage u1 ON "
7713
8.77k
                    "u1.object_table_name = v1.table_name AND "
7714
8.77k
                    "u1.object_auth_name = v1.auth_name AND "
7715
8.77k
                    "u1.object_code = v1.code "
7716
8.77k
                    "JOIN extent a1 "
7717
8.77k
                    "ON a1.auth_name = u1.extent_auth_name AND "
7718
8.77k
                    "a1.code = u1.extent_code "
7719
8.77k
                    "JOIN usage u2 ON "
7720
8.77k
                    "u2.object_table_name = v2.table_name AND "
7721
8.77k
                    "u2.object_auth_name = v2.auth_name AND "
7722
8.77k
                    "u2.object_code = v2.code "
7723
8.77k
                    "JOIN extent a2 "
7724
8.77k
                    "ON a2.auth_name = u2.extent_auth_name AND "
7725
8.77k
                    "a2.code = u2.extent_code "));
7726
8.77k
    const std::string orderBy(
7727
8.77k
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7728
8.77k
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7729
8.77k
        "accuracy1 + accuracy2");
7730
7731
    // Case (source->intermediate) and (intermediate->target)
7732
8.77k
    std::string sql(
7733
8.77k
        sqlProlog +
7734
8.77k
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7735
8.77k
        "AND v1.target_crs_code = v2.source_crs_code " +
7736
8.77k
        joinArea +
7737
8.77k
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7738
8.77k
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7739
8.77k
    std::string minDate;
7740
8.77k
    std::string criterionOnIntermediateCRS;
7741
7742
8.77k
    const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7743
8.77k
                               ->createCoordinateReferenceSystem(sourceCRSCode);
7744
8.77k
    const auto targetCRS = d->createFactory(targetCRSAuthName)
7745
8.77k
                               ->createCoordinateReferenceSystem(targetCRSCode);
7746
7747
8.77k
    const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7748
1.50k
                            starts_with(targetCRS->nameStr(), "ETRF");
7749
7750
8.77k
    const bool NAD83_CSRS_to_NAD83_CSRS =
7751
8.77k
        starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") &&
7752
2
        starts_with(targetCRS->nameStr(), "NAD83(CSRS)");
7753
7754
8.77k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7755
6.08k
        const auto &sourceGeogCRS =
7756
6.08k
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7757
6.08k
        const auto &targetGeogCRS =
7758
6.08k
            dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7759
6.08k
        if (sourceGeogCRS && targetGeogCRS) {
7760
6.08k
            const auto &sourceDatum = sourceGeogCRS->datum();
7761
6.08k
            const auto &targetDatum = targetGeogCRS->datum();
7762
6.08k
            if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7763
2.07k
                targetDatum && targetDatum->publicationDate().has_value()) {
7764
701
                const auto sourceDate(
7765
701
                    sourceDatum->publicationDate()->toString());
7766
701
                const auto targetDate(
7767
701
                    targetDatum->publicationDate()->toString());
7768
701
                minDate = std::min(sourceDate, targetDate);
7769
                // Check that the datum of the intermediateCRS has a publication
7770
                // date most recent that the one of the source and the target
7771
                // CRS Except when using the usual WGS84 pivot which happens to
7772
                // have a NULL publication date.
7773
701
                criterionOnIntermediateCRS =
7774
701
                    "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7775
701
                    "JOIN geodetic_datum y "
7776
701
                    "ON "
7777
701
                    "y.auth_name = x.datum_auth_name AND "
7778
701
                    "y.code = x.datum_code "
7779
701
                    "WHERE "
7780
701
                    "x.auth_name = v1.target_crs_auth_name AND "
7781
701
                    "x.code = v1.target_crs_code AND "
7782
701
                    "x.type IN ('geographic 2D', 'geographic 3D') AND "
7783
701
                    "(y.publication_date IS NULL OR "
7784
701
                    "(y.publication_date >= '" +
7785
701
                    minDate + "'))) ";
7786
701
            }
7787
6.08k
        }
7788
6.08k
        if (criterionOnIntermediateCRS.empty()) {
7789
5.38k
            criterionOnIntermediateCRS =
7790
5.38k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7791
5.38k
                "x.auth_name = v1.target_crs_auth_name AND "
7792
5.38k
                "x.code = v1.target_crs_code AND "
7793
5.38k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7794
5.38k
        }
7795
6.08k
        sql += criterionOnIntermediateCRS;
7796
6.08k
    }
7797
8.77k
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7798
8.77k
                               targetCRSAuthName, targetCRSCode};
7799
8.77k
    std::string additionalWhere(
7800
8.77k
        skipIntermediateExtentIntersection
7801
8.77k
            ? "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7802
8.77k
            : "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7803
8.77k
              "AND intersects_bbox(south_lat1, west_lon1, north_lat1, "
7804
8.77k
              "east_lon1, south_lat2, west_lon2, north_lat2, "
7805
8.77k
              "east_lon2) = 1 ");
7806
8.77k
    if (!allowedAuthorities.empty()) {
7807
6.89k
        additionalWhere += "AND v1.auth_name IN (";
7808
27.5k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7809
20.6k
            if (i > 0)
7810
13.7k
                additionalWhere += ',';
7811
20.6k
            additionalWhere += '?';
7812
20.6k
        }
7813
6.89k
        additionalWhere += ") AND v2.auth_name IN (";
7814
27.5k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7815
20.6k
            if (i > 0)
7816
13.7k
                additionalWhere += ',';
7817
20.6k
            additionalWhere += '?';
7818
20.6k
        }
7819
6.89k
        additionalWhere += ')';
7820
20.6k
        for (const auto &allowedAuthority : allowedAuthorities) {
7821
20.6k
            params.emplace_back(allowedAuthority);
7822
20.6k
        }
7823
20.6k
        for (const auto &allowedAuthority : allowedAuthorities) {
7824
20.6k
            params.emplace_back(allowedAuthority);
7825
20.6k
        }
7826
6.89k
    }
7827
8.77k
    if (d->hasAuthorityRestriction()) {
7828
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7829
0
        params.emplace_back(d->authority());
7830
0
        params.emplace_back(d->authority());
7831
0
    }
7832
17.5k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7833
17.5k
        if (extent) {
7834
8.67k
            const auto &geogExtent = extent->geographicElements();
7835
8.67k
            if (geogExtent.size() == 1) {
7836
8.67k
                auto bbox =
7837
8.67k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7838
8.67k
                        geogExtent[0].get());
7839
8.67k
                if (bbox) {
7840
8.67k
                    const double south_lat = bbox->southBoundLatitude();
7841
8.67k
                    const double west_lon = bbox->westBoundLongitude();
7842
8.67k
                    const double north_lat = bbox->northBoundLatitude();
7843
8.67k
                    const double east_lon = bbox->eastBoundLongitude();
7844
8.67k
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7845
5.98k
                        north_lat != 90.0 || east_lon != 180.0) {
7846
5.98k
                        additionalWhere +=
7847
5.98k
                            "AND intersects_bbox(south_lat1, "
7848
5.98k
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7849
5.98k
                            "intersects_bbox(south_lat2, west_lon2, "
7850
5.98k
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7851
5.98k
                        params.emplace_back(south_lat);
7852
5.98k
                        params.emplace_back(west_lon);
7853
5.98k
                        params.emplace_back(north_lat);
7854
5.98k
                        params.emplace_back(east_lon);
7855
5.98k
                        params.emplace_back(south_lat);
7856
5.98k
                        params.emplace_back(west_lon);
7857
5.98k
                        params.emplace_back(north_lat);
7858
5.98k
                        params.emplace_back(east_lon);
7859
5.98k
                    }
7860
8.67k
                }
7861
8.67k
            }
7862
8.67k
        }
7863
17.5k
    }
7864
7865
8.77k
    const auto buildIntermediateWhere =
7866
8.77k
        [&intermediateCRSAuthCodes](const std::string &first_field,
7867
35.0k
                                    const std::string &second_field) {
7868
35.0k
            if (intermediateCRSAuthCodes.empty()) {
7869
35.0k
                return std::string();
7870
35.0k
            }
7871
0
            std::string l_sql(" AND (");
7872
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7873
0
                if (i > 0) {
7874
0
                    l_sql += " OR";
7875
0
                }
7876
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7877
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7878
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7879
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7880
0
            }
7881
0
            l_sql += ')';
7882
0
            return l_sql;
7883
35.0k
        };
7884
7885
8.77k
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7886
8.77k
    for (const auto &pair : intermediateCRSAuthCodes) {
7887
0
        params.emplace_back(pair.first);
7888
0
        params.emplace_back(pair.second);
7889
0
        params.emplace_back(pair.first);
7890
0
        params.emplace_back(pair.second);
7891
0
    }
7892
8.77k
    auto res =
7893
8.77k
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7894
7895
35.0k
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7896
35.0k
        std::set<std::pair<std::string, std::string>> setTransf1;
7897
35.0k
        std::set<std::pair<std::string, std::string>> setTransf2;
7898
35.0k
        for (const auto &row : resultSet) {
7899
            // table1
7900
23.4k
            const auto &auth_name1 = row[1];
7901
23.4k
            const auto &code1 = row[2];
7902
            // accuracy1
7903
            // table2
7904
23.4k
            const auto &auth_name2 = row[5];
7905
23.4k
            const auto &code2 = row[6];
7906
23.4k
            setTransf1.insert(
7907
23.4k
                std::pair<std::string, std::string>(auth_name1, code1));
7908
23.4k
            setTransf2.insert(
7909
23.4k
                std::pair<std::string, std::string>(auth_name2, code2));
7910
23.4k
        }
7911
35.0k
        SQLResultSet filteredResultSet;
7912
35.0k
        for (const auto &row : resultSet) {
7913
23.4k
            const auto &replacement_auth_name1 = row[16];
7914
23.4k
            const auto &replacement_code1 = row[17];
7915
23.4k
            const auto &replacement_auth_name2 = row[18];
7916
23.4k
            const auto &replacement_code2 = row[19];
7917
23.4k
            if (!replacement_auth_name1.empty() &&
7918
34
                setTransf1.find(std::pair<std::string, std::string>(
7919
34
                    replacement_auth_name1, replacement_code1)) !=
7920
34
                    setTransf1.end()) {
7921
                // Skip transformations that are superseded by others that got
7922
                // returned in the result set.
7923
27
                continue;
7924
27
            }
7925
23.4k
            if (!replacement_auth_name2.empty() &&
7926
15
                setTransf2.find(std::pair<std::string, std::string>(
7927
15
                    replacement_auth_name2, replacement_code2)) !=
7928
15
                    setTransf2.end()) {
7929
                // Skip transformations that are superseded by others that got
7930
                // returned in the result set.
7931
11
                continue;
7932
11
            }
7933
23.3k
            filteredResultSet.emplace_back(row);
7934
23.3k
        }
7935
35.0k
        return filteredResultSet;
7936
35.0k
    };
7937
7938
8.77k
    if (discardSuperseded) {
7939
8.77k
        res = filterOutSuperseded(std::move(res));
7940
8.77k
    }
7941
7942
8.77k
    const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS,
7943
17.6k
                             &targetCRS](const crs::CRSPtr &intermediateCRS) {
7944
        // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or
7945
        // ITRF>2014
7946
17.6k
        if (ETRFtoETRF && intermediateCRS &&
7947
16
            starts_with(intermediateCRS->nameStr(), "ITRF")) {
7948
48
            const auto normalizeDate = [](int v) {
7949
48
                return (v >= 80 && v <= 99) ? v + 1900 : v;
7950
48
            };
7951
16
            const int srcDate = normalizeDate(
7952
16
                atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7953
16
            const int tgtDate = normalizeDate(
7954
16
                atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7955
16
            const int intermDate = normalizeDate(
7956
16
                atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7957
16
            if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7958
16
                if (intermDate < std::min(srcDate, tgtDate) ||
7959
8
                    intermDate > std::max(srcDate, tgtDate)) {
7960
8
                    return false;
7961
8
                }
7962
16
            }
7963
16
        }
7964
7965
        // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through
7966
        // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464
7967
17.6k
        if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS &&
7968
0
            (intermediateCRS->nameStr() == "NAD83" ||
7969
0
             intermediateCRS->nameStr() == "WGS 84")) {
7970
0
            return false;
7971
0
        }
7972
7973
17.6k
        return true;
7974
17.6k
    };
7975
7976
8.77k
    for (const auto &row : res) {
7977
18
        const auto &table1 = row[0];
7978
18
        const auto &auth_name1 = row[1];
7979
18
        const auto &code1 = row[2];
7980
        // const auto &accuracy1 = row[3];
7981
18
        const auto &table2 = row[4];
7982
18
        const auto &auth_name2 = row[5];
7983
18
        const auto &code2 = row[6];
7984
        // const auto &accuracy2 = row[7];
7985
18
        try {
7986
18
            auto op1 =
7987
18
                d->createFactory(auth_name1)
7988
18
                    ->createCoordinateOperation(
7989
18
                        code1, true, usePROJAlternativeGridNames, table1);
7990
18
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7991
18
                                   targetCRSAuthName, targetCRSCode)) {
7992
0
                continue;
7993
0
            }
7994
18
            if (!checkPivot(op1->targetCRS())) {
7995
0
                continue;
7996
0
            }
7997
18
            auto op2 =
7998
18
                d->createFactory(auth_name2)
7999
18
                    ->createCoordinateOperation(
8000
18
                        code2, true, usePROJAlternativeGridNames, table2);
8001
18
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8002
18
                                   targetCRSAuthName, targetCRSCode)) {
8003
0
                continue;
8004
0
            }
8005
8006
18
            listTmp.emplace_back(
8007
18
                operation::ConcatenatedOperation::createComputeMetadata(
8008
18
                    {std::move(op1), std::move(op2)}, false));
8009
18
        } catch (const std::exception &e) {
8010
            // Mostly for debugging purposes when using an inconsistent
8011
            // database
8012
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8013
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8014
0
            } else {
8015
0
                throw;
8016
0
            }
8017
0
        }
8018
18
    }
8019
8020
    // Case (source->intermediate) and (target->intermediate)
8021
8.77k
    sql = sqlProlog +
8022
8.77k
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
8023
8.77k
          "AND v1.target_crs_code = v2.target_crs_code " +
8024
8.77k
          joinArea +
8025
8.77k
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
8026
8.77k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8027
8.77k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8028
6.08k
        sql += criterionOnIntermediateCRS;
8029
6.08k
    }
8030
8.77k
    intermediateWhere = buildIntermediateWhere("target", "target");
8031
8.77k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8032
8.77k
    if (discardSuperseded) {
8033
8.77k
        res = filterOutSuperseded(std::move(res));
8034
8.77k
    }
8035
8036
23.1k
    for (const auto &row : res) {
8037
23.1k
        const auto &table1 = row[0];
8038
23.1k
        const auto &auth_name1 = row[1];
8039
23.1k
        const auto &code1 = row[2];
8040
        // const auto &accuracy1 = row[3];
8041
23.1k
        const auto &table2 = row[4];
8042
23.1k
        const auto &auth_name2 = row[5];
8043
23.1k
        const auto &code2 = row[6];
8044
        // const auto &accuracy2 = row[7];
8045
23.1k
        try {
8046
23.1k
            auto op1 =
8047
23.1k
                d->createFactory(auth_name1)
8048
23.1k
                    ->createCoordinateOperation(
8049
23.1k
                        code1, true, usePROJAlternativeGridNames, table1);
8050
23.1k
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8051
23.1k
                                   targetCRSAuthName, targetCRSCode)) {
8052
5.73k
                continue;
8053
5.73k
            }
8054
17.4k
            if (!checkPivot(op1->targetCRS())) {
8055
0
                continue;
8056
0
            }
8057
17.4k
            auto op2 =
8058
17.4k
                d->createFactory(auth_name2)
8059
17.4k
                    ->createCoordinateOperation(
8060
17.4k
                        code2, true, usePROJAlternativeGridNames, table2);
8061
17.4k
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8062
17.4k
                                   targetCRSAuthName, targetCRSCode)) {
8063
9.55k
                continue;
8064
9.55k
            }
8065
8066
7.87k
            listTmp.emplace_back(
8067
7.87k
                operation::ConcatenatedOperation::createComputeMetadata(
8068
7.87k
                    {std::move(op1), op2->inverse()}, false));
8069
7.87k
        } catch (const std::exception &e) {
8070
            // Mostly for debugging purposes when using an inconsistent
8071
            // database
8072
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8073
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8074
0
            } else {
8075
0
                throw;
8076
0
            }
8077
0
        }
8078
23.1k
    }
8079
8080
    // Case (intermediate->source) and (intermediate->target)
8081
8.77k
    sql = sqlProlog +
8082
8.77k
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
8083
8.77k
          "AND v1.source_crs_code = v2.source_crs_code " +
8084
8.77k
          joinArea +
8085
8.77k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8086
8.77k
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
8087
8.77k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8088
6.08k
        if (!minDate.empty()) {
8089
701
            criterionOnIntermediateCRS =
8090
701
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
8091
701
                "JOIN geodetic_datum y "
8092
701
                "ON "
8093
701
                "y.auth_name = x.datum_auth_name AND "
8094
701
                "y.code = x.datum_code "
8095
701
                "WHERE "
8096
701
                "x.auth_name = v1.source_crs_auth_name AND "
8097
701
                "x.code = v1.source_crs_code AND "
8098
701
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
8099
701
                "(y.publication_date IS NULL OR "
8100
701
                "(y.publication_date >= '" +
8101
701
                minDate + "'))) ";
8102
5.38k
        } else {
8103
5.38k
            criterionOnIntermediateCRS =
8104
5.38k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
8105
5.38k
                "x.auth_name = v1.source_crs_auth_name AND "
8106
5.38k
                "x.code = v1.source_crs_code AND "
8107
5.38k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
8108
5.38k
        }
8109
6.08k
        sql += criterionOnIntermediateCRS;
8110
6.08k
    }
8111
8.77k
    intermediateWhere = buildIntermediateWhere("source", "source");
8112
8.77k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8113
8.77k
    if (discardSuperseded) {
8114
8.77k
        res = filterOutSuperseded(std::move(res));
8115
8.77k
    }
8116
8.77k
    for (const auto &row : res) {
8117
190
        const auto &table1 = row[0];
8118
190
        const auto &auth_name1 = row[1];
8119
190
        const auto &code1 = row[2];
8120
        // const auto &accuracy1 = row[3];
8121
190
        const auto &table2 = row[4];
8122
190
        const auto &auth_name2 = row[5];
8123
190
        const auto &code2 = row[6];
8124
        // const auto &accuracy2 = row[7];
8125
190
        try {
8126
190
            auto op1 =
8127
190
                d->createFactory(auth_name1)
8128
190
                    ->createCoordinateOperation(
8129
190
                        code1, true, usePROJAlternativeGridNames, table1);
8130
190
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8131
190
                                   targetCRSAuthName, targetCRSCode)) {
8132
0
                continue;
8133
0
            }
8134
190
            if (!checkPivot(op1->sourceCRS())) {
8135
8
                continue;
8136
8
            }
8137
182
            auto op2 =
8138
182
                d->createFactory(auth_name2)
8139
182
                    ->createCoordinateOperation(
8140
182
                        code2, true, usePROJAlternativeGridNames, table2);
8141
182
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8142
182
                                   targetCRSAuthName, targetCRSCode)) {
8143
4
                continue;
8144
4
            }
8145
8146
178
            listTmp.emplace_back(
8147
178
                operation::ConcatenatedOperation::createComputeMetadata(
8148
178
                    {op1->inverse(), std::move(op2)}, false));
8149
178
        } catch (const std::exception &e) {
8150
            // Mostly for debugging purposes when using an inconsistent
8151
            // database
8152
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8153
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8154
0
            } else {
8155
0
                throw;
8156
0
            }
8157
0
        }
8158
190
    }
8159
8160
    // Case (intermediate->source) and (target->intermediate)
8161
8.77k
    sql = sqlProlog +
8162
8.77k
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
8163
8.77k
          "AND v1.source_crs_code = v2.target_crs_code " +
8164
8.77k
          joinArea +
8165
8.77k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8166
8.77k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8167
8.77k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8168
6.08k
        sql += criterionOnIntermediateCRS;
8169
6.08k
    }
8170
8.77k
    intermediateWhere = buildIntermediateWhere("source", "target");
8171
8.77k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8172
8.77k
    if (discardSuperseded) {
8173
8.77k
        res = filterOutSuperseded(std::move(res));
8174
8.77k
    }
8175
8.77k
    for (const auto &row : res) {
8176
21
        const auto &table1 = row[0];
8177
21
        const auto &auth_name1 = row[1];
8178
21
        const auto &code1 = row[2];
8179
        // const auto &accuracy1 = row[3];
8180
21
        const auto &table2 = row[4];
8181
21
        const auto &auth_name2 = row[5];
8182
21
        const auto &code2 = row[6];
8183
        // const auto &accuracy2 = row[7];
8184
21
        try {
8185
21
            auto op1 =
8186
21
                d->createFactory(auth_name1)
8187
21
                    ->createCoordinateOperation(
8188
21
                        code1, true, usePROJAlternativeGridNames, table1);
8189
21
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8190
21
                                   targetCRSAuthName, targetCRSCode)) {
8191
0
                continue;
8192
0
            }
8193
21
            if (!checkPivot(op1->sourceCRS())) {
8194
0
                continue;
8195
0
            }
8196
21
            auto op2 =
8197
21
                d->createFactory(auth_name2)
8198
21
                    ->createCoordinateOperation(
8199
21
                        code2, true, usePROJAlternativeGridNames, table2);
8200
21
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8201
21
                                   targetCRSAuthName, targetCRSCode)) {
8202
0
                continue;
8203
0
            }
8204
8205
21
            listTmp.emplace_back(
8206
21
                operation::ConcatenatedOperation::createComputeMetadata(
8207
21
                    {op1->inverse(), op2->inverse()}, false));
8208
21
        } catch (const std::exception &e) {
8209
            // Mostly for debugging purposes when using an inconsistent
8210
            // database
8211
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8212
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8213
0
            } else {
8214
0
                throw;
8215
0
            }
8216
0
        }
8217
21
    }
8218
8219
8.77k
    std::vector<operation::CoordinateOperationNNPtr> list;
8220
8.77k
    for (const auto &op : listTmp) {
8221
8.09k
        if (!discardIfMissingGrid ||
8222
8.09k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8223
851
            list.emplace_back(op);
8224
851
        }
8225
8.09k
    }
8226
8227
8.77k
    return list;
8228
8.77k
}
8229
8230
// ---------------------------------------------------------------------------
8231
8232
//! @cond Doxygen_Suppress
8233
8234
struct TrfmInfo {
8235
    std::string situation{};
8236
    std::string table_name{};
8237
    std::string auth_name{};
8238
    std::string code{};
8239
    std::string name{};
8240
    double west = 0;
8241
    double south = 0;
8242
    double east = 0;
8243
    double north = 0;
8244
};
8245
8246
// ---------------------------------------------------------------------------
8247
8248
std::vector<operation::CoordinateOperationNNPtr>
8249
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
8250
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
8251
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
8252
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
8253
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
8254
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
8255
    const std::vector<std::string> &allowedAuthorities,
8256
    const metadata::ExtentPtr &intersectingExtent1,
8257
    const metadata::ExtentPtr &intersectingExtent2,
8258
359
    bool skipIntermediateExtentIntersection) const {
8259
8260
359
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
8261
8262
359
    if (sourceCRSAuthName == targetCRSAuthName &&
8263
222
        sourceCRSCode == targetCRSCode) {
8264
0
        return listTmp;
8265
0
    }
8266
359
    const auto sourceGeodCRS =
8267
359
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
8268
359
    const auto targetGeodCRS =
8269
359
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
8270
359
    if (!sourceGeodCRS || !targetGeodCRS) {
8271
0
        return listTmp;
8272
0
    }
8273
8274
359
    const bool NAD83_CSRS_to_NAD83_CSRS =
8275
359
        starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") &&
8276
0
        starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)");
8277
8278
359
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
8279
359
                                                const std::string &crsAuthName,
8280
718
                                                const std::string &crsCode) {
8281
        // Find all geodetic CRS that share the same datum as the CRS
8282
718
        SQLResultSet listCRS;
8283
8284
718
        const common::IdentifiedObject *obj = crs->datum().get();
8285
718
        if (obj == nullptr)
8286
171
            obj = crs->datumEnsemble().get();
8287
718
        assert(obj != nullptr);
8288
718
        const auto &ids = obj->identifiers();
8289
718
        std::string datumAuthName;
8290
718
        std::string datumCode;
8291
718
        if (!ids.empty()) {
8292
718
            const auto &id = ids.front();
8293
718
            datumAuthName = *(id->codeSpace());
8294
718
            datumCode = id->code();
8295
718
        } else {
8296
0
            const auto res =
8297
0
                d->run("SELECT datum_auth_name, datum_code FROM "
8298
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
8299
0
                       {crsAuthName, crsCode});
8300
0
            if (res.size() != 1) {
8301
0
                return listCRS;
8302
0
            }
8303
0
            const auto &row = res.front();
8304
0
            datumAuthName = row[0];
8305
0
            datumCode = row[1];
8306
0
        }
8307
8308
718
        listCRS =
8309
718
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
8310
718
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
8311
718
                   {datumAuthName, datumCode});
8312
718
        if (listCRS.empty()) {
8313
            // Can happen if the CRS is deprecated
8314
13
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
8315
13
        }
8316
718
        return listCRS;
8317
718
    };
8318
8319
359
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
8320
359
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
8321
359
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
8322
359
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
8323
359
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
8324
        // would happen only if we had CRS objects in the database without a
8325
        // link to a datum.
8326
0
        return listTmp;
8327
0
    }
8328
8329
359
    ListOfParams params;
8330
359
    const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS,
8331
359
                               &allowedAuthorities, &params, &listSourceCRS,
8332
359
                               &listTargetCRS](bool isSourceCRS,
8333
1.43k
                                               bool selectOnTarget) {
8334
1.43k
        std::string situation;
8335
1.43k
        if (isSourceCRS)
8336
718
            situation = "src";
8337
718
        else
8338
718
            situation = "tgt";
8339
1.43k
        if (selectOnTarget)
8340
718
            situation += "_is_tgt";
8341
718
        else
8342
718
            situation += "_is_src";
8343
1.43k
        const std::string prefix1(selectOnTarget ? "source" : "target");
8344
1.43k
        const std::string prefix2(selectOnTarget ? "target" : "source");
8345
1.43k
        std::string sql("SELECT '");
8346
1.43k
        sql += situation;
8347
1.43k
        sql += "' as situation, v.table_name, v.auth_name, "
8348
1.43k
               "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
8349
1.43k
               "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
8350
1.43k
               "FROM coordinate_operation_view v "
8351
1.43k
               "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
8352
1.43k
        sql += prefix1;
8353
1.43k
        sql += "_crs_auth_name AND gcrs.code = ";
8354
1.43k
        sql += prefix1;
8355
1.43k
        sql += "_crs_code "
8356
8357
1.43k
               "LEFT JOIN usage u ON "
8358
1.43k
               "u.object_table_name = v.table_name AND "
8359
1.43k
               "u.object_auth_name = v.auth_name AND "
8360
1.43k
               "u.object_code = v.code "
8361
1.43k
               "LEFT JOIN extent a "
8362
1.43k
               "ON a.auth_name = u.extent_auth_name AND "
8363
1.43k
               "a.code = u.extent_code "
8364
1.43k
               "WHERE v.deprecated = 0 AND (";
8365
8366
1.43k
        std::string cond;
8367
8368
1.43k
        const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
8369
7.52k
        for (const auto &row : list) {
8370
7.52k
            if (!cond.empty())
8371
6.08k
                cond += " OR ";
8372
7.52k
            cond += '(';
8373
7.52k
            cond += prefix2;
8374
7.52k
            cond += "_crs_auth_name = ? AND ";
8375
7.52k
            cond += prefix2;
8376
7.52k
            cond += "_crs_code = ?)";
8377
7.52k
            params.emplace_back(row[0]);
8378
7.52k
            params.emplace_back(row[1]);
8379
7.52k
        }
8380
8381
1.43k
        sql += cond;
8382
1.43k
        sql += ") ";
8383
8384
1.43k
        if (!allowedAuthorities.empty()) {
8385
1.28k
            sql += "AND v.auth_name IN (";
8386
5.12k
            for (size_t i = 0; i < allowedAuthorities.size(); i++) {
8387
3.84k
                if (i > 0)
8388
2.56k
                    sql += ',';
8389
3.84k
                sql += '?';
8390
3.84k
            }
8391
1.28k
            sql += ") ";
8392
3.84k
            for (const auto &allowedAuthority : allowedAuthorities) {
8393
3.84k
                params.emplace_back(allowedAuthority);
8394
3.84k
            }
8395
1.28k
        }
8396
1.43k
        if (d->hasAuthorityRestriction()) {
8397
0
            sql += "AND v.auth_name = ? ";
8398
0
            params.emplace_back(d->authority());
8399
0
        }
8400
1.43k
        if (NAD83_CSRS_to_NAD83_CSRS) {
8401
            // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go
8402
            // through NAD83 generic. Cf
8403
            // https://github.com/OSGeo/PROJ/issues/4464
8404
0
            sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') ";
8405
0
        }
8406
8407
1.43k
        return sql;
8408
1.43k
    };
8409
8410
359
    std::string sql(BuildSQLPart(true, true));
8411
359
    sql += "UNION ALL ";
8412
359
    sql += BuildSQLPart(false, true);
8413
359
    sql += "UNION ALL ";
8414
359
    sql += BuildSQLPart(true, false);
8415
359
    sql += "UNION ALL ";
8416
359
    sql += BuildSQLPart(false, false);
8417
    // fprintf(stderr, "sql : %s\n", sql.c_str());
8418
8419
    // Find all operations that have as source/target CRS a CRS that
8420
    // share the same datum as the source or targetCRS
8421
359
    const auto res = d->run(sql, params);
8422
8423
359
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
8424
359
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
8425
8426
277k
    for (const auto &row : res) {
8427
277k
        try {
8428
277k
            TrfmInfo trfm;
8429
277k
            trfm.situation = row[0];
8430
277k
            trfm.table_name = row[1];
8431
277k
            trfm.auth_name = row[2];
8432
277k
            trfm.code = row[3];
8433
277k
            trfm.name = row[4];
8434
277k
            const auto &datum_auth_name = row[5];
8435
277k
            const auto &datum_code = row[6];
8436
277k
            trfm.west = c_locale_stod(row[7]);
8437
277k
            trfm.south = c_locale_stod(row[8]);
8438
277k
            trfm.east = c_locale_stod(row[9]);
8439
277k
            trfm.north = c_locale_stod(row[10]);
8440
277k
            const std::string key =
8441
277k
                std::string(datum_auth_name).append(":").append(datum_code);
8442
277k
            if (trfm.situation == "src_is_tgt" ||
8443
24.6k
                trfm.situation == "src_is_src")
8444
263k
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
8445
13.7k
            else
8446
13.7k
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
8447
277k
        } catch (const std::exception &) {
8448
0
        }
8449
277k
    }
8450
8451
359
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
8452
718
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
8453
718
        if (extent) {
8454
363
            const auto &geogExtent = extent->geographicElements();
8455
363
            if (geogExtent.size() == 1) {
8456
363
                auto bbox =
8457
363
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
8458
363
                        geogExtent[0].get());
8459
363
                if (bbox) {
8460
363
                    const double south_lat = bbox->southBoundLatitude();
8461
363
                    const double west_lon = bbox->westBoundLongitude();
8462
363
                    const double north_lat = bbox->northBoundLatitude();
8463
363
                    const double east_lon = bbox->eastBoundLongitude();
8464
363
                    if (south_lat != -90.0 || west_lon != -180.0 ||
8465
276
                        north_lat != 90.0 || east_lon != 180.0) {
8466
276
                        extraBbox.emplace_back(bbox);
8467
276
                    }
8468
363
                }
8469
363
            }
8470
363
        }
8471
718
    }
8472
8473
359
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
8474
359
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
8475
359
    std::map<std::string, TrfmInfo> setOfTransformations;
8476
8477
14.2k
    const auto MakeKey = [](const TrfmInfo &trfm) {
8478
14.2k
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
8479
14.2k
    };
8480
8481
    // Find transformations that share a pivot datum, and do bbox filtering
8482
98.1k
    for (const auto &kvSource : mapIntermDatumOfSource) {
8483
98.1k
        const auto &listTrmfSource = kvSource.second;
8484
98.1k
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
8485
98.1k
        if (iter == mapIntermDatumOfTarget.end())
8486
95.7k
            continue;
8487
8488
2.46k
        const auto &listTrfmTarget = iter->second;
8489
5.42k
        for (const auto &trfmSource : listTrmfSource) {
8490
5.42k
            auto bbox1 = metadata::GeographicBoundingBox::create(
8491
5.42k
                trfmSource.west, trfmSource.south, trfmSource.east,
8492
5.42k
                trfmSource.north);
8493
5.42k
            bool okBbox1 = true;
8494
5.42k
            for (const auto bbox : extraBbox)
8495
3.18k
                okBbox1 &= bbox->intersects(bbox1);
8496
5.42k
            if (!okBbox1)
8497
819
                continue;
8498
8499
4.60k
            const std::string key1 = MakeKey(trfmSource);
8500
8501
30.5k
            for (const auto &trfmTarget : listTrfmTarget) {
8502
30.5k
                auto bbox2 = metadata::GeographicBoundingBox::create(
8503
30.5k
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
8504
30.5k
                    trfmTarget.north);
8505
30.5k
                if (!skipIntermediateExtentIntersection &&
8506
30.5k
                    !bbox1->intersects(bbox2))
8507
27.3k
                    continue;
8508
3.22k
                bool okBbox2 = true;
8509
3.22k
                for (const auto bbox : extraBbox)
8510
2.65k
                    okBbox2 &= bbox->intersects(bbox2);
8511
3.22k
                if (!okBbox2)
8512
0
                    continue;
8513
8514
3.22k
                operation::CoordinateOperationPtr op1;
8515
3.22k
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
8516
2.64k
                    auto op1NN = d->createFactory(trfmSource.auth_name)
8517
2.64k
                                     ->createCoordinateOperation(
8518
2.64k
                                         trfmSource.code, true,
8519
2.64k
                                         usePROJAlternativeGridNames,
8520
2.64k
                                         trfmSource.table_name);
8521
2.64k
                    op1 = op1NN.as_nullable();
8522
2.64k
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
8523
2.64k
                                           sourceCRSCode, targetCRSAuthName,
8524
2.64k
                                           targetCRSCode)) {
8525
2
                        op1.reset();
8526
2
                    }
8527
2.64k
                    oMapTrfmKeyToOp[key1] = op1;
8528
2.64k
                } else {
8529
577
                    op1 = oMapTrfmKeyToOp[key1];
8530
577
                }
8531
3.22k
                if (op1 == nullptr)
8532
2
                    continue;
8533
8534
3.22k
                const std::string key2 = MakeKey(trfmTarget);
8535
8536
3.22k
                operation::CoordinateOperationPtr op2;
8537
3.22k
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
8538
2.53k
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
8539
2.53k
                                     ->createCoordinateOperation(
8540
2.53k
                                         trfmTarget.code, true,
8541
2.53k
                                         usePROJAlternativeGridNames,
8542
2.53k
                                         trfmTarget.table_name);
8543
2.53k
                    op2 = op2NN.as_nullable();
8544
2.53k
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
8545
2.53k
                                           sourceCRSCode, targetCRSAuthName,
8546
2.53k
                                           targetCRSCode)) {
8547
3
                        op2.reset();
8548
3
                    }
8549
2.53k
                    oMapTrfmKeyToOp[key2] = op2;
8550
2.53k
                } else {
8551
692
                    op2 = oMapTrfmKeyToOp[key2];
8552
692
                }
8553
3.22k
                if (op2 == nullptr)
8554
4
                    continue;
8555
8556
3.21k
                candidates.emplace_back(
8557
3.21k
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
8558
3.21k
                setOfTransformations[key1] = trfmSource;
8559
3.21k
                setOfTransformations[key2] = trfmTarget;
8560
3.21k
            }
8561
4.60k
        }
8562
2.46k
    }
8563
8564
359
    std::set<std::string> setSuperseded;
8565
359
    if (discardSuperseded && !setOfTransformations.empty()) {
8566
146
        std::string findSupersededSql(
8567
146
            "SELECT superseded_table_name, "
8568
146
            "superseded_auth_name, superseded_code, "
8569
146
            "replacement_auth_name, replacement_code "
8570
146
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
8571
146
        bool findSupersededFirstWhere = true;
8572
146
        ListOfParams findSupersededParams;
8573
8574
146
        const auto keyMapSupersession = [](const std::string &table_name,
8575
146
                                           const std::string &auth_name,
8576
5.18k
                                           const std::string &code) {
8577
5.18k
            return table_name + auth_name + code;
8578
5.18k
        };
8579
8580
146
        std::set<std::pair<std::string, std::string>> setTransf;
8581
5.17k
        for (const auto &kv : setOfTransformations) {
8582
5.17k
            const auto &table = kv.second.table_name;
8583
5.17k
            const auto &auth_name = kv.second.auth_name;
8584
5.17k
            const auto &code = kv.second.code;
8585
8586
5.17k
            if (!findSupersededFirstWhere)
8587
5.02k
                findSupersededSql += " OR ";
8588
5.17k
            findSupersededFirstWhere = false;
8589
5.17k
            findSupersededSql +=
8590
5.17k
                "(superseded_table_name = ? AND replacement_table_name = "
8591
5.17k
                "superseded_table_name AND superseded_auth_name = ? AND "
8592
5.17k
                "superseded_code = ?)";
8593
5.17k
            findSupersededParams.push_back(table);
8594
5.17k
            findSupersededParams.push_back(auth_name);
8595
5.17k
            findSupersededParams.push_back(code);
8596
8597
5.17k
            setTransf.insert(
8598
5.17k
                std::pair<std::string, std::string>(auth_name, code));
8599
5.17k
        }
8600
146
        findSupersededSql += ')';
8601
8602
146
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
8603
146
            mapSupersession;
8604
8605
146
        const auto resSuperseded =
8606
146
            d->run(findSupersededSql, findSupersededParams);
8607
146
        for (const auto &row : resSuperseded) {
8608
13
            const auto &superseded_table_name = row[0];
8609
13
            const auto &superseded_auth_name = row[1];
8610
13
            const auto &superseded_code = row[2];
8611
13
            const auto &replacement_auth_name = row[3];
8612
13
            const auto &replacement_code = row[4];
8613
13
            mapSupersession[keyMapSupersession(superseded_table_name,
8614
13
                                               superseded_auth_name,
8615
13
                                               superseded_code)]
8616
13
                .push_back(std::pair<std::string, std::string>(
8617
13
                    replacement_auth_name, replacement_code));
8618
13
        }
8619
8620
5.17k
        for (const auto &kv : setOfTransformations) {
8621
5.17k
            const auto &table = kv.second.table_name;
8622
5.17k
            const auto &auth_name = kv.second.auth_name;
8623
5.17k
            const auto &code = kv.second.code;
8624
8625
5.17k
            const auto iter = mapSupersession.find(
8626
5.17k
                keyMapSupersession(table, auth_name, code));
8627
5.17k
            if (iter != mapSupersession.end()) {
8628
13
                bool foundReplacement = false;
8629
13
                for (const auto &replacement : iter->second) {
8630
13
                    const auto &replacement_auth_name = replacement.first;
8631
13
                    const auto &replacement_code = replacement.second;
8632
13
                    if (setTransf.find(std::pair<std::string, std::string>(
8633
13
                            replacement_auth_name, replacement_code)) !=
8634
13
                        setTransf.end()) {
8635
                        // Skip transformations that are superseded by others
8636
                        // that got
8637
                        // returned in the result set.
8638
13
                        foundReplacement = true;
8639
13
                        break;
8640
13
                    }
8641
13
                }
8642
13
                if (foundReplacement) {
8643
13
                    setSuperseded.insert(kv.first);
8644
13
                }
8645
13
            }
8646
5.17k
        }
8647
146
    }
8648
8649
359
    std::string sourceDatumPubDate;
8650
359
    const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context());
8651
359
    if (sourceDatum->publicationDate().has_value()) {
8652
128
        sourceDatumPubDate = sourceDatum->publicationDate()->toString();
8653
128
    }
8654
8655
359
    std::string targetDatumPubDate;
8656
359
    const auto targetDatum = targetGeodCRS->datumNonNull(d->context());
8657
359
    if (targetDatum->publicationDate().has_value()) {
8658
327
        targetDatumPubDate = targetDatum->publicationDate()->toString();
8659
327
    }
8660
8661
359
    const std::string mostAncientDatumPubDate =
8662
359
        (!targetDatumPubDate.empty() &&
8663
327
         (sourceDatumPubDate.empty() ||
8664
121
          targetDatumPubDate < sourceDatumPubDate))
8665
359
            ? targetDatumPubDate
8666
359
            : sourceDatumPubDate;
8667
8668
359
    auto opFactory = operation::CoordinateOperationFactory::create();
8669
3.21k
    for (const auto &pair : candidates) {
8670
3.21k
        const auto &trfmSource = pair.first;
8671
3.21k
        const auto &trfmTarget = pair.second;
8672
3.21k
        const std::string key1 = MakeKey(trfmSource);
8673
3.21k
        const std::string key2 = MakeKey(trfmTarget);
8674
3.21k
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8675
3.21k
            setSuperseded.find(key2) != setSuperseded.end()) {
8676
23
            continue;
8677
23
        }
8678
3.19k
        auto op1 = oMapTrfmKeyToOp[key1];
8679
3.19k
        auto op2 = oMapTrfmKeyToOp[key2];
8680
3.19k
        auto op1NN = NN_NO_CHECK(op1);
8681
3.19k
        auto op2NN = NN_NO_CHECK(op2);
8682
3.19k
        if (trfmSource.situation == "src_is_tgt")
8683
3.00k
            op1NN = op1NN->inverse();
8684
3.19k
        if (trfmTarget.situation == "tgt_is_src")
8685
1.33k
            op2NN = op2NN->inverse();
8686
8687
3.19k
        const auto &op1Source = op1NN->sourceCRS();
8688
3.19k
        const auto &op1Target = op1NN->targetCRS();
8689
3.19k
        const auto &op2Source = op2NN->sourceCRS();
8690
3.19k
        const auto &op2Target = op2NN->targetCRS();
8691
3.19k
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8692
0
            continue;
8693
0
        }
8694
8695
        // Skip operations using a datum that is older than the source or
8696
        // target datum (e.g to avoid ED50 to WGS84 to go through NTF)
8697
3.19k
        if (!mostAncientDatumPubDate.empty()) {
8698
3.18k
            const auto isOlderCRS = [this, &mostAncientDatumPubDate](
8699
12.7k
                                        const crs::CRSPtr &crs) {
8700
12.7k
                const auto geogCRS =
8701
12.7k
                    dynamic_cast<const crs::GeodeticCRS *>(crs.get());
8702
12.7k
                if (geogCRS) {
8703
12.7k
                    const auto datum = geogCRS->datumNonNull(d->context());
8704
                    // Hum, theoretically we'd want to check
8705
                    // datum->publicationDate()->toString() <
8706
                    // mostAncientDatumPubDate but that would exclude doing
8707
                    // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008,
8708
                    // since IG05/12 Intermediate CRS has been published later
8709
                    // than ITRF2008. So use a cut of date for ancient vs
8710
                    // "modern" era.
8711
12.7k
                    constexpr const char *CUT_OFF_DATE = "1900-01-01";
8712
12.7k
                    if (datum->publicationDate().has_value() &&
8713
6.06k
                        datum->publicationDate()->toString() < CUT_OFF_DATE &&
8714
0
                        mostAncientDatumPubDate > CUT_OFF_DATE) {
8715
0
                        return true;
8716
0
                    }
8717
12.7k
                }
8718
12.7k
                return false;
8719
12.7k
            };
8720
8721
3.18k
            if (isOlderCRS(op1Source) || isOlderCRS(op1Target) ||
8722
3.18k
                isOlderCRS(op2Source) || isOlderCRS(op2Target))
8723
0
                continue;
8724
3.18k
        }
8725
8726
3.19k
        std::vector<operation::CoordinateOperationNNPtr> steps;
8727
8728
3.19k
        if (!sourceCRS->isEquivalentTo(
8729
3.19k
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8730
904
            auto opFirst =
8731
904
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8732
904
            assert(opFirst);
8733
904
            steps.emplace_back(NN_NO_CHECK(opFirst));
8734
904
        }
8735
8736
3.19k
        steps.emplace_back(op1NN);
8737
8738
3.19k
        if (!op1Target->isEquivalentTo(
8739
3.19k
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8740
2.85k
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8741
2.85k
                                                       NN_NO_CHECK(op2Source));
8742
2.85k
            assert(opMiddle);
8743
2.85k
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8744
2.85k
        }
8745
8746
3.19k
        steps.emplace_back(op2NN);
8747
8748
3.19k
        if (!op2Target->isEquivalentTo(
8749
3.19k
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8750
2.29k
            auto opLast =
8751
2.29k
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8752
2.29k
            assert(opLast);
8753
2.29k
            steps.emplace_back(NN_NO_CHECK(opLast));
8754
2.29k
        }
8755
8756
3.19k
        listTmp.emplace_back(
8757
3.19k
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8758
3.19k
                                                                    false));
8759
3.19k
    }
8760
8761
359
    std::vector<operation::CoordinateOperationNNPtr> list;
8762
3.19k
    for (const auto &op : listTmp) {
8763
3.19k
        if (!discardIfMissingGrid ||
8764
3.19k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8765
2.23k
            list.emplace_back(op);
8766
2.23k
        }
8767
3.19k
    }
8768
8769
359
    return list;
8770
359
}
8771
8772
//! @endcond
8773
8774
// ---------------------------------------------------------------------------
8775
8776
/** \brief Returns the authority name associated to this factory.
8777
 * @return name.
8778
 */
8779
109k
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8780
109k
    return d->authority();
8781
109k
}
8782
8783
// ---------------------------------------------------------------------------
8784
8785
/** \brief Returns the set of authority codes of the given object type.
8786
 *
8787
 * @param type Object type.
8788
 * @param allowDeprecated whether we should return deprecated objects as well.
8789
 * @return the set of authority codes for spatial reference objects of the given
8790
 * type
8791
 * @throw FactoryException in case of error.
8792
 */
8793
std::set<std::string>
8794
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8795
0
                                    bool allowDeprecated) const {
8796
0
    std::string sql;
8797
0
    switch (type) {
8798
0
    case ObjectType::PRIME_MERIDIAN:
8799
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8800
0
        break;
8801
0
    case ObjectType::ELLIPSOID:
8802
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8803
0
        break;
8804
0
    case ObjectType::DATUM:
8805
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8806
0
              "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND ";
8807
0
        break;
8808
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8809
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8810
0
        break;
8811
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8812
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8813
0
              "frame_reference_epoch IS NOT NULL AND ";
8814
0
        break;
8815
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8816
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8817
0
        break;
8818
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8819
0
        sql = "SELECT code FROM vertical_datum WHERE "
8820
0
              "frame_reference_epoch IS NOT NULL AND ";
8821
0
        break;
8822
0
    case ObjectType::ENGINEERING_DATUM:
8823
0
        sql = "SELECT code FROM engineering_datum WHERE ";
8824
0
        break;
8825
0
    case ObjectType::CRS:
8826
0
        sql = "SELECT code FROM crs_view WHERE ";
8827
0
        break;
8828
0
    case ObjectType::GEODETIC_CRS:
8829
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8830
0
        break;
8831
0
    case ObjectType::GEOCENTRIC_CRS:
8832
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8833
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8834
0
        break;
8835
0
    case ObjectType::GEOGRAPHIC_CRS:
8836
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8837
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8838
0
        break;
8839
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8840
0
        sql =
8841
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8842
0
            " AND ";
8843
0
        break;
8844
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8845
0
        sql =
8846
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8847
0
            " AND ";
8848
0
        break;
8849
0
    case ObjectType::VERTICAL_CRS:
8850
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8851
0
        break;
8852
0
    case ObjectType::PROJECTED_CRS:
8853
0
        sql = "SELECT code FROM projected_crs WHERE ";
8854
0
        break;
8855
0
    case ObjectType::COMPOUND_CRS:
8856
0
        sql = "SELECT code FROM compound_crs WHERE ";
8857
0
        break;
8858
0
    case ObjectType::ENGINEERING_CRS:
8859
0
        sql = "SELECT code FROM engineering_crs WHERE ";
8860
0
        break;
8861
0
    case ObjectType::DERIVED_PROJECTED_CRS:
8862
0
        sql = "SELECT code FROM derived_projected_crs WHERE ";
8863
0
        break;
8864
0
    case ObjectType::COORDINATE_OPERATION:
8865
0
        sql =
8866
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8867
0
        break;
8868
0
    case ObjectType::CONVERSION:
8869
0
        sql = "SELECT code FROM conversion WHERE ";
8870
0
        break;
8871
0
    case ObjectType::TRANSFORMATION:
8872
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8873
0
              "'concatenated_operation' AND ";
8874
0
        break;
8875
0
    case ObjectType::CONCATENATED_OPERATION:
8876
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8877
0
        break;
8878
0
    case ObjectType::DATUM_ENSEMBLE:
8879
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8880
0
              "('geodetic_datum', 'vertical_datum') AND "
8881
0
              "type = 'ensemble' AND ";
8882
0
        break;
8883
0
    }
8884
8885
0
    sql += "auth_name = ?";
8886
0
    if (!allowDeprecated) {
8887
0
        sql += " AND deprecated = 0";
8888
0
    }
8889
8890
0
    auto res = d->run(sql, {d->authority()});
8891
0
    std::set<std::string> set;
8892
0
    for (const auto &row : res) {
8893
0
        set.insert(row[0]);
8894
0
    }
8895
0
    return set;
8896
0
}
8897
8898
// ---------------------------------------------------------------------------
8899
8900
/** \brief Gets a description of the object corresponding to a code.
8901
 *
8902
 * \note In case of several objects of different types with the same code,
8903
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8904
 * will be selected.
8905
 *
8906
 * @param code Object code allocated by authority. (e.g. "4326")
8907
 * @return description.
8908
 * @throw NoSuchAuthorityCodeException if there is no matching object.
8909
 * @throw FactoryException in case of other errors.
8910
 */
8911
std::string
8912
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8913
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8914
0
               "AND code = ? ORDER BY table_name";
8915
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8916
0
    if (sqlRes.empty()) {
8917
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8918
0
                                           code);
8919
0
    }
8920
0
    std::string text;
8921
0
    for (const auto &row : sqlRes) {
8922
0
        const auto &tableName = row[1];
8923
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8924
0
            tableName == "vertical_crs" || tableName == "compound_crs" ||
8925
0
            tableName == "engineering_crs" ||
8926
0
            tableName == "derived_projected_crs") {
8927
0
            return row[0];
8928
0
        } else if (text.empty()) {
8929
0
            text = row[0];
8930
0
        }
8931
0
    }
8932
0
    return text;
8933
0
}
8934
8935
// ---------------------------------------------------------------------------
8936
8937
/** \brief Return a list of information on CRS objects
8938
 *
8939
 * This is functionally equivalent of listing the codes from an authority,
8940
 * instantiating
8941
 * a CRS object for each of them and getting the information from this CRS
8942
 * object, but this implementation has much less overhead.
8943
 *
8944
 * @throw FactoryException in case of error.
8945
 */
8946
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8947
8948
0
    const auto getSqlArea = [](const char *table_name) {
8949
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8950
0
        sql += table_name;
8951
0
        sql += "' AND "
8952
0
               "u.object_auth_name = c.auth_name AND "
8953
0
               "u.object_code = c.code "
8954
0
               "LEFT JOIN extent a "
8955
0
               "ON a.auth_name = u.extent_auth_name AND "
8956
0
               "a.code = u.extent_code ";
8957
0
        return sql;
8958
0
    };
8959
8960
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8961
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8962
0
        sql += crs_alias;
8963
0
        sql += ".datum_auth_name AND gd.code = ";
8964
0
        sql += crs_alias;
8965
0
        sql += ".datum_code "
8966
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8967
0
               "AND e.code = gd.ellipsoid_code "
8968
0
               "LEFT JOIN celestial_body cb ON "
8969
0
               "cb.auth_name = e.celestial_body_auth_name "
8970
0
               "AND cb.code = e.celestial_body_code ";
8971
0
        return sql;
8972
0
    };
8973
8974
0
    std::string sql = "SELECT * FROM ("
8975
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8976
0
                      "c.deprecated, "
8977
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8978
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8979
0
    sql += getSqlArea("geodetic_crs");
8980
0
    sql += getJoinCelestialBody("c");
8981
0
    ListOfParams params;
8982
0
    if (d->hasAuthorityRestriction()) {
8983
0
        sql += "WHERE c.auth_name = ? ";
8984
0
        params.emplace_back(d->authority());
8985
0
    }
8986
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8987
0
           "c.deprecated, "
8988
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8989
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8990
0
           "projected_crs c "
8991
0
           "LEFT JOIN conversion_table conv ON "
8992
0
           "c.conversion_auth_name = conv.auth_name AND "
8993
0
           "c.conversion_code = conv.code "
8994
0
           "LEFT JOIN conversion_method cm ON "
8995
0
           "conv.method_auth_name = cm.auth_name AND "
8996
0
           "conv.method_code = cm.code "
8997
0
           "LEFT JOIN geodetic_crs gcrs ON "
8998
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
8999
0
           "AND gcrs.code = c.geodetic_crs_code ";
9000
0
    sql += getSqlArea("projected_crs");
9001
0
    sql += getJoinCelestialBody("gcrs");
9002
0
    if (d->hasAuthorityRestriction()) {
9003
0
        sql += "WHERE c.auth_name = ? ";
9004
0
        params.emplace_back(d->authority());
9005
0
    }
9006
    // FIXME: we can't handle non-EARTH vertical CRS for now
9007
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
9008
0
           "c.deprecated, "
9009
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9010
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
9011
0
    sql += getSqlArea("vertical_crs");
9012
0
    if (d->hasAuthorityRestriction()) {
9013
0
        sql += "WHERE c.auth_name = ? ";
9014
0
        params.emplace_back(d->authority());
9015
0
    }
9016
    // FIXME: we can't handle non-EARTH compound CRS for now
9017
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
9018
0
           "c.deprecated, "
9019
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9020
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
9021
0
    sql += getSqlArea("compound_crs");
9022
0
    if (d->hasAuthorityRestriction()) {
9023
0
        sql += "WHERE c.auth_name = ? ";
9024
0
        params.emplace_back(d->authority());
9025
0
    }
9026
    // FIXME: we can't handle non-EARTH compound CRS for now
9027
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', "
9028
0
           "c.deprecated, "
9029
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9030
0
           "a.description, NULL, 'Earth' FROM engineering_crs c ";
9031
0
    sql += getSqlArea("engineering_crs");
9032
0
    if (d->hasAuthorityRestriction()) {
9033
0
        sql += "WHERE c.auth_name = ? ";
9034
0
        params.emplace_back(d->authority());
9035
0
    }
9036
    // FIXME: we can't handle non-EARTH compound CRS for now
9037
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'derived projected', "
9038
0
           "c.deprecated, "
9039
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9040
0
           "a.description, NULL, 'Earth' FROM derived_projected_crs c ";
9041
0
    sql += getSqlArea("derived_projected_crs");
9042
0
    if (d->hasAuthorityRestriction()) {
9043
0
        sql += "WHERE c.auth_name = ? ";
9044
0
        params.emplace_back(d->authority());
9045
0
    }
9046
0
    sql += ") r ORDER BY auth_name, code";
9047
0
    auto sqlRes = d->run(sql, params);
9048
0
    std::list<AuthorityFactory::CRSInfo> res;
9049
0
    for (const auto &row : sqlRes) {
9050
0
        AuthorityFactory::CRSInfo info;
9051
0
        info.authName = row[0];
9052
0
        info.code = row[1];
9053
0
        info.name = row[2];
9054
0
        const auto &type = row[3];
9055
0
        if (type == CRS_SUBTYPE_GEOG_2D) {
9056
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
9057
0
        } else if (type == CRS_SUBTYPE_GEOG_3D) {
9058
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
9059
0
        } else if (type == CRS_SUBTYPE_GEOCENTRIC) {
9060
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
9061
0
        } else if (type == CRS_SUBTYPE_OTHER) {
9062
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
9063
0
        } else if (type == CRS_SUBTYPE_PROJECTED) {
9064
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
9065
0
        } else if (type == CRS_SUBTYPE_VERTICAL) {
9066
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
9067
0
        } else if (type == CRS_SUBTYPE_COMPOUND) {
9068
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
9069
0
        } else if (type == CRS_SUBTYPE_ENGINEERING) {
9070
0
            info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS;
9071
0
        } else if (type == CRS_SUBTYPE_DERIVED_PROJECTED) {
9072
0
            info.type = AuthorityFactory::ObjectType::DERIVED_PROJECTED_CRS;
9073
0
        }
9074
0
        info.deprecated = row[4] == "1";
9075
0
        if (row[5].empty()) {
9076
0
            info.bbox_valid = false;
9077
0
        } else {
9078
0
            info.bbox_valid = true;
9079
0
            info.west_lon_degree = c_locale_stod(row[5]);
9080
0
            info.south_lat_degree = c_locale_stod(row[6]);
9081
0
            info.east_lon_degree = c_locale_stod(row[7]);
9082
0
            info.north_lat_degree = c_locale_stod(row[8]);
9083
0
        }
9084
0
        info.areaName = row[9];
9085
0
        info.projectionMethodName = row[10];
9086
0
        info.celestialBodyName = row[11];
9087
0
        res.emplace_back(info);
9088
0
    }
9089
0
    return res;
9090
0
}
9091
9092
// ---------------------------------------------------------------------------
9093
9094
//! @cond Doxygen_Suppress
9095
AuthorityFactory::UnitInfo::UnitInfo()
9096
0
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
9097
0
      deprecated{} {}
9098
//! @endcond
9099
9100
// ---------------------------------------------------------------------------
9101
9102
//! @cond Doxygen_Suppress
9103
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
9104
//! @endcond
9105
9106
// ---------------------------------------------------------------------------
9107
9108
/** \brief Return the list of units.
9109
 * @throw FactoryException in case of error.
9110
 *
9111
 * @since 7.1
9112
 */
9113
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
9114
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
9115
0
                      "proj_short_name, deprecated FROM unit_of_measure";
9116
0
    ListOfParams params;
9117
0
    if (d->hasAuthorityRestriction()) {
9118
0
        sql += " WHERE auth_name = ?";
9119
0
        params.emplace_back(d->authority());
9120
0
    }
9121
0
    sql += " ORDER BY auth_name, code";
9122
9123
0
    auto sqlRes = d->run(sql, params);
9124
0
    std::list<AuthorityFactory::UnitInfo> res;
9125
0
    for (const auto &row : sqlRes) {
9126
0
        AuthorityFactory::UnitInfo info;
9127
0
        info.authName = row[0];
9128
0
        info.code = row[1];
9129
0
        info.name = row[2];
9130
0
        const std::string &raw_category(row[3]);
9131
0
        if (raw_category == "length") {
9132
0
            info.category = info.name.find(" per ") != std::string::npos
9133
0
                                ? "linear_per_time"
9134
0
                                : "linear";
9135
0
        } else if (raw_category == "angle") {
9136
0
            info.category = info.name.find(" per ") != std::string::npos
9137
0
                                ? "angular_per_time"
9138
0
                                : "angular";
9139
0
        } else if (raw_category == "scale") {
9140
0
            info.category =
9141
0
                info.name.find(" per year") != std::string::npos ||
9142
0
                        info.name.find(" per second") != std::string::npos
9143
0
                    ? "scale_per_time"
9144
0
                    : "scale";
9145
0
        } else {
9146
0
            info.category = raw_category;
9147
0
        }
9148
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
9149
0
        info.projShortName = row[5];
9150
0
        info.deprecated = row[6] == "1";
9151
0
        res.emplace_back(info);
9152
0
    }
9153
0
    return res;
9154
0
}
9155
9156
// ---------------------------------------------------------------------------
9157
9158
/** \brief Return the list of celestial bodies.
9159
 * @throw FactoryException in case of error.
9160
 *
9161
 * @since 8.1
9162
 */
9163
std::list<AuthorityFactory::CelestialBodyInfo>
9164
0
AuthorityFactory::getCelestialBodyList() const {
9165
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
9166
0
    ListOfParams params;
9167
0
    if (d->hasAuthorityRestriction()) {
9168
0
        sql += " WHERE auth_name = ?";
9169
0
        params.emplace_back(d->authority());
9170
0
    }
9171
0
    sql += " ORDER BY auth_name, name";
9172
9173
0
    auto sqlRes = d->run(sql, params);
9174
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
9175
0
    for (const auto &row : sqlRes) {
9176
0
        AuthorityFactory::CelestialBodyInfo info;
9177
0
        info.authName = row[0];
9178
0
        info.name = row[1];
9179
0
        res.emplace_back(info);
9180
0
    }
9181
0
    return res;
9182
0
}
9183
9184
// ---------------------------------------------------------------------------
9185
9186
/** \brief Gets the official name from a possibly alias name.
9187
 *
9188
 * @param aliasedName Alias name.
9189
 * @param tableName Table name/category. Can help in case of ambiguities.
9190
 * Or empty otherwise.
9191
 * @param source Source of the alias. Can help in case of ambiguities.
9192
 * Or empty otherwise.
9193
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
9194
 * the alt_name column of the alias_name table should be done with using
9195
 * metadata::Identifier::isEquivalentName() rather than strict string
9196
 * comparison;
9197
 * @param outTableName Table name in which the official name has been found.
9198
 * @param outAuthName Authority name of the official name that has been found.
9199
 * @param outCode Code of the official name that has been found.
9200
 * @return official name (or empty if not found).
9201
 * @throw FactoryException in case of error.
9202
 */
9203
std::string AuthorityFactory::getOfficialNameFromAlias(
9204
    const std::string &aliasedName, const std::string &tableName,
9205
    const std::string &source, bool tryEquivalentNameSpelling,
9206
    std::string &outTableName, std::string &outAuthName,
9207
3.26k
    std::string &outCode) const {
9208
9209
3.26k
    if (tryEquivalentNameSpelling) {
9210
0
        std::string sql(
9211
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
9212
0
        ListOfParams params;
9213
0
        if (!tableName.empty()) {
9214
0
            sql += " WHERE table_name = ?";
9215
0
            params.push_back(tableName);
9216
0
        }
9217
0
        if (!source.empty()) {
9218
0
            if (!tableName.empty()) {
9219
0
                sql += " AND ";
9220
0
            } else {
9221
0
                sql += " WHERE ";
9222
0
            }
9223
0
            sql += "source = ?";
9224
0
            params.push_back(source);
9225
0
        }
9226
0
        auto res = d->run(sql, params);
9227
0
        if (res.empty()) {
9228
0
            return std::string();
9229
0
        }
9230
0
        for (const auto &row : res) {
9231
0
            const auto &alt_name = row[3];
9232
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
9233
0
                                                       aliasedName.c_str())) {
9234
0
                outTableName = row[0];
9235
0
                outAuthName = row[1];
9236
0
                outCode = row[2];
9237
0
                sql = "SELECT name FROM \"";
9238
0
                sql += replaceAll(outTableName, "\"", "\"\"");
9239
0
                sql += "\" WHERE auth_name = ? AND code = ?";
9240
0
                res = d->run(sql, {outAuthName, outCode});
9241
0
                if (res.empty()) { // shouldn't happen normally
9242
0
                    return std::string();
9243
0
                }
9244
0
                return res.front()[0];
9245
0
            }
9246
0
        }
9247
0
        return std::string();
9248
3.26k
    } else {
9249
3.26k
        std::string sql(
9250
3.26k
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
9251
3.26k
            "alt_name = ?");
9252
3.26k
        ListOfParams params{aliasedName};
9253
3.26k
        if (!tableName.empty()) {
9254
3.26k
            sql += " AND table_name = ?";
9255
3.26k
            params.push_back(tableName);
9256
3.26k
        }
9257
3.26k
        if (!source.empty()) {
9258
3.26k
            if (source == "ESRI") {
9259
3.26k
                sql += " AND source IN ('ESRI', 'ESRI_OLD')";
9260
3.26k
            } else {
9261
0
                sql += " AND source = ?";
9262
0
                params.push_back(source);
9263
0
            }
9264
3.26k
        }
9265
3.26k
        auto res = d->run(sql, params);
9266
3.26k
        if (res.empty()) {
9267
2.15k
            return std::string();
9268
2.15k
        }
9269
9270
1.10k
        params.clear();
9271
1.10k
        sql.clear();
9272
1.10k
        bool first = true;
9273
1.10k
        for (const auto &row : res) {
9274
1.10k
            if (!first)
9275
3
                sql += " UNION ALL ";
9276
1.10k
            first = false;
9277
1.10k
            outTableName = row[0];
9278
1.10k
            outAuthName = row[1];
9279
1.10k
            outCode = row[2];
9280
1.10k
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
9281
1.10k
                   "FROM \"";
9282
1.10k
            sql += replaceAll(outTableName, "\"", "\"\"");
9283
1.10k
            sql += "\" WHERE auth_name = ? AND code = ?";
9284
1.10k
            params.emplace_back(outTableName);
9285
1.10k
            params.emplace_back(outAuthName);
9286
1.10k
            params.emplace_back(outCode);
9287
1.10k
        }
9288
1.10k
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
9289
1.10k
              ") x ORDER BY deprecated LIMIT 1";
9290
1.10k
        res = d->run(sql, params);
9291
1.10k
        if (res.empty()) { // shouldn't happen normally
9292
0
            return std::string();
9293
0
        }
9294
1.10k
        const auto &row = res.front();
9295
1.10k
        outTableName = row[1];
9296
1.10k
        outAuthName = row[2];
9297
1.10k
        outCode = row[3];
9298
1.10k
        return row[0];
9299
1.10k
    }
9300
3.26k
}
9301
9302
// ---------------------------------------------------------------------------
9303
9304
/** \brief Return a list of objects, identified by their name
9305
 *
9306
 * @param searchedName Searched name. Must be at least 2 character long.
9307
 * @param allowedObjectTypes List of object types into which to search. If
9308
 * empty, all object types will be searched.
9309
 * @param approximateMatch Whether approximate name identification is allowed.
9310
 * @param limitResultCount Maximum number of results to return.
9311
 * Or 0 for unlimited.
9312
 * @return list of matched objects.
9313
 * @throw FactoryException in case of error.
9314
 */
9315
std::list<common::IdentifiedObjectNNPtr>
9316
AuthorityFactory::createObjectsFromName(
9317
    const std::string &searchedName,
9318
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9319
59.0k
    size_t limitResultCount) const {
9320
59.0k
    std::list<common::IdentifiedObjectNNPtr> res;
9321
59.0k
    const auto resTmp(createObjectsFromNameEx(
9322
59.0k
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
9323
59.0k
    for (const auto &pair : resTmp) {
9324
30.4k
        res.emplace_back(pair.first);
9325
30.4k
    }
9326
59.0k
    return res;
9327
59.0k
}
9328
9329
// ---------------------------------------------------------------------------
9330
9331
//! @cond Doxygen_Suppress
9332
9333
/** \brief Return a list of objects, identifier by their name, with the name
9334
 * on which the match occurred.
9335
 *
9336
 * The name on which the match occurred might be different from the object name,
9337
 * if the match has been done on an alias name of that object.
9338
 *
9339
 * @param searchedName Searched name. Must be at least 2 character long.
9340
 * @param allowedObjectTypes List of object types into which to search. If
9341
 * empty, all object types will be searched.
9342
 * @param approximateMatch Whether approximate name identification is allowed.
9343
 * @param limitResultCount Maximum number of results to return.
9344
 * Or 0 for unlimited.
9345
 * @param useAliases Whether querying the alias_name table is allowed
9346
 * @return list of matched objects.
9347
 * @throw FactoryException in case of error.
9348
 */
9349
std::list<AuthorityFactory::PairObjectName>
9350
AuthorityFactory::createObjectsFromNameEx(
9351
    const std::string &searchedName,
9352
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9353
59.0k
    size_t limitResultCount, bool useAliases) const {
9354
59.0k
    std::string searchedNameWithoutDeprecated(searchedName);
9355
59.0k
    bool deprecated = false;
9356
59.0k
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
9357
0
        deprecated = true;
9358
0
        searchedNameWithoutDeprecated.resize(
9359
0
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
9360
0
    }
9361
9362
59.0k
    const std::string canonicalizedSearchedName(
9363
59.0k
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
9364
59.0k
    if (canonicalizedSearchedName.size() <= 1) {
9365
2.00k
        return {};
9366
2.00k
    }
9367
9368
57.0k
    std::string sql(
9369
57.0k
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
9370
57.0k
        "FROM (");
9371
9372
57.0k
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
9373
57.0k
                                             &searchedName]() {
9374
57.0k
        typedef std::pair<std::string, std::string> TableType;
9375
57.0k
        std::list<TableType> res;
9376
        // Hide ESRI D_ vertical datums
9377
57.0k
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
9378
57.0k
        if (allowedObjectTypes.empty()) {
9379
0
            for (const auto &tableName :
9380
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
9381
0
                  "vertical_datum", "engineering_datum", "geodetic_crs",
9382
0
                  "projected_crs", "derived_projected_crs", "vertical_crs",
9383
0
                  "compound_crs", "engineering_crs", "conversion",
9384
0
                  "helmert_transformation", "grid_transformation",
9385
0
                  "other_transformation", "concatenated_operation"}) {
9386
0
                if (!(startsWithDUnderscore &&
9387
0
                      strcmp(tableName, "vertical_datum") == 0)) {
9388
0
                    res.emplace_back(TableType(tableName, std::string()));
9389
0
                }
9390
0
            }
9391
57.0k
        } else {
9392
65.9k
            for (const auto type : allowedObjectTypes) {
9393
65.9k
                switch (type) {
9394
0
                case ObjectType::PRIME_MERIDIAN:
9395
0
                    res.emplace_back(
9396
0
                        TableType("prime_meridian", std::string()));
9397
0
                    break;
9398
3.89k
                case ObjectType::ELLIPSOID:
9399
3.89k
                    res.emplace_back(TableType("ellipsoid", std::string()));
9400
3.89k
                    break;
9401
2.96k
                case ObjectType::DATUM:
9402
2.96k
                    res.emplace_back(
9403
2.96k
                        TableType("geodetic_datum", std::string()));
9404
2.96k
                    if (!startsWithDUnderscore) {
9405
2.95k
                        res.emplace_back(
9406
2.95k
                            TableType("vertical_datum", std::string()));
9407
2.95k
                        res.emplace_back(
9408
2.95k
                            TableType("engineering_datum", std::string()));
9409
2.95k
                    }
9410
2.96k
                    break;
9411
15.1k
                case ObjectType::GEODETIC_REFERENCE_FRAME:
9412
15.1k
                    res.emplace_back(
9413
15.1k
                        TableType("geodetic_datum", std::string()));
9414
15.1k
                    break;
9415
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
9416
0
                    res.emplace_back(
9417
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
9418
0
                    break;
9419
1.81k
                case ObjectType::VERTICAL_REFERENCE_FRAME:
9420
1.81k
                    res.emplace_back(
9421
1.81k
                        TableType("vertical_datum", std::string()));
9422
1.81k
                    break;
9423
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
9424
0
                    res.emplace_back(
9425
0
                        TableType("vertical_datum", "frame_reference_epoch"));
9426
0
                    break;
9427
0
                case ObjectType::ENGINEERING_DATUM:
9428
0
                    res.emplace_back(
9429
0
                        TableType("engineering_datum", std::string()));
9430
0
                    break;
9431
3.85k
                case ObjectType::CRS:
9432
3.85k
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9433
3.85k
                    res.emplace_back(TableType("projected_crs", std::string()));
9434
3.85k
                    res.emplace_back(TableType("vertical_crs", std::string()));
9435
3.85k
                    res.emplace_back(TableType("compound_crs", std::string()));
9436
3.85k
                    res.emplace_back(
9437
3.85k
                        TableType("engineering_crs", std::string()));
9438
3.85k
                    res.emplace_back(
9439
3.85k
                        TableType("derived_projected_crs", std::string()));
9440
3.85k
                    break;
9441
0
                case ObjectType::GEODETIC_CRS:
9442
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9443
0
                    break;
9444
0
                case ObjectType::GEOCENTRIC_CRS:
9445
0
                    res.emplace_back(
9446
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOCENTRIC));
9447
0
                    break;
9448
0
                case ObjectType::GEOGRAPHIC_CRS:
9449
0
                    res.emplace_back(
9450
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_2D));
9451
0
                    res.emplace_back(
9452
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_3D));
9453
0
                    break;
9454
8.47k
                case ObjectType::GEOGRAPHIC_2D_CRS:
9455
8.47k
                    res.emplace_back(
9456
8.47k
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_2D));
9457
8.47k
                    break;
9458
23.2k
                case ObjectType::GEOGRAPHIC_3D_CRS:
9459
23.2k
                    res.emplace_back(
9460
23.2k
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_3D));
9461
23.2k
                    break;
9462
207
                case ObjectType::PROJECTED_CRS:
9463
207
                    res.emplace_back(TableType("projected_crs", std::string()));
9464
207
                    break;
9465
0
                case ObjectType::DERIVED_PROJECTED_CRS:
9466
0
                    res.emplace_back(
9467
0
                        TableType("derived_projected_crs", std::string()));
9468
0
                    break;
9469
267
                case ObjectType::VERTICAL_CRS:
9470
267
                    res.emplace_back(TableType("vertical_crs", std::string()));
9471
267
                    break;
9472
131
                case ObjectType::COMPOUND_CRS:
9473
131
                    res.emplace_back(TableType("compound_crs", std::string()));
9474
131
                    break;
9475
0
                case ObjectType::ENGINEERING_CRS:
9476
0
                    res.emplace_back(
9477
0
                        TableType("engineering_crs", std::string()));
9478
0
                    break;
9479
2.96k
                case ObjectType::COORDINATE_OPERATION:
9480
2.96k
                    res.emplace_back(TableType("conversion", std::string()));
9481
2.96k
                    res.emplace_back(
9482
2.96k
                        TableType("helmert_transformation", std::string()));
9483
2.96k
                    res.emplace_back(
9484
2.96k
                        TableType("grid_transformation", std::string()));
9485
2.96k
                    res.emplace_back(
9486
2.96k
                        TableType("other_transformation", std::string()));
9487
2.96k
                    res.emplace_back(
9488
2.96k
                        TableType("concatenated_operation", std::string()));
9489
2.96k
                    break;
9490
0
                case ObjectType::CONVERSION:
9491
0
                    res.emplace_back(TableType("conversion", std::string()));
9492
0
                    break;
9493
0
                case ObjectType::TRANSFORMATION:
9494
0
                    res.emplace_back(
9495
0
                        TableType("helmert_transformation", std::string()));
9496
0
                    res.emplace_back(
9497
0
                        TableType("grid_transformation", std::string()));
9498
0
                    res.emplace_back(
9499
0
                        TableType("other_transformation", std::string()));
9500
0
                    break;
9501
0
                case ObjectType::CONCATENATED_OPERATION:
9502
0
                    res.emplace_back(
9503
0
                        TableType("concatenated_operation", std::string()));
9504
0
                    break;
9505
2.97k
                case ObjectType::DATUM_ENSEMBLE:
9506
2.97k
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
9507
2.97k
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
9508
2.97k
                    break;
9509
65.9k
                }
9510
65.9k
            }
9511
57.0k
        }
9512
57.0k
        return res;
9513
57.0k
    };
9514
9515
57.0k
    bool datumEnsembleAllowed = false;
9516
57.0k
    if (allowedObjectTypes.empty()) {
9517
0
        datumEnsembleAllowed = true;
9518
57.0k
    } else {
9519
62.9k
        for (const auto type : allowedObjectTypes) {
9520
62.9k
            if (type == ObjectType::DATUM_ENSEMBLE) {
9521
2.97k
                datumEnsembleAllowed = true;
9522
2.97k
                break;
9523
2.97k
            }
9524
62.9k
        }
9525
57.0k
    }
9526
9527
57.0k
    const auto listTableNameType = getTableAndTypeConstraints();
9528
57.0k
    bool first = true;
9529
57.0k
    ListOfParams params;
9530
105k
    for (const auto &tableNameTypePair : listTableNameType) {
9531
105k
        if (!first) {
9532
48.8k
            sql += " UNION ";
9533
48.8k
        }
9534
105k
        first = false;
9535
105k
        sql += "SELECT '";
9536
105k
        sql += tableNameTypePair.first;
9537
105k
        sql += "' AS table_name, auth_name, code, name, deprecated, "
9538
105k
               "0 AS is_alias FROM ";
9539
105k
        sql += tableNameTypePair.first;
9540
105k
        sql += " WHERE 1 = 1 ";
9541
105k
        if (!tableNameTypePair.second.empty()) {
9542
37.6k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9543
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
9544
37.6k
            } else if (tableNameTypePair.second == "ensemble") {
9545
5.94k
                sql += "AND ensemble_accuracy IS NOT NULL ";
9546
31.7k
            } else {
9547
31.7k
                sql += "AND type = '";
9548
31.7k
                sql += tableNameTypePair.second;
9549
31.7k
                sql += "' ";
9550
31.7k
            }
9551
37.6k
        }
9552
105k
        if (deprecated) {
9553
0
            sql += "AND deprecated = 1 ";
9554
0
        }
9555
105k
        if (!approximateMatch) {
9556
84.7k
            sql += "AND name = ? COLLATE NOCASE ";
9557
84.7k
            params.push_back(searchedNameWithoutDeprecated);
9558
84.7k
        }
9559
105k
        if (d->hasAuthorityRestriction()) {
9560
24.1k
            sql += "AND auth_name = ? ";
9561
24.1k
            params.emplace_back(d->authority());
9562
24.1k
        }
9563
9564
105k
        if (useAliases) {
9565
105k
            sql += " UNION SELECT '";
9566
105k
            sql += tableNameTypePair.first;
9567
105k
            sql += "' AS table_name, "
9568
105k
                   "ov.auth_name AS auth_name, "
9569
105k
                   "ov.code AS code, a.alt_name AS name, "
9570
105k
                   "ov.deprecated AS deprecated, 1 as is_alias FROM ";
9571
105k
            sql += tableNameTypePair.first;
9572
105k
            sql += " ov "
9573
105k
                   "JOIN alias_name a ON "
9574
105k
                   "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
9575
105k
                   "a.source != 'EPSG_OLD' AND a.table_name = '";
9576
105k
            sql += tableNameTypePair.first;
9577
105k
            sql += "' ";
9578
105k
            if (!tableNameTypePair.second.empty()) {
9579
37.6k
                if (tableNameTypePair.second == "frame_reference_epoch") {
9580
0
                    sql += "AND ov.frame_reference_epoch IS NOT NULL ";
9581
37.6k
                } else if (tableNameTypePair.second == "ensemble") {
9582
5.94k
                    sql += "AND ov.ensemble_accuracy IS NOT NULL ";
9583
31.7k
                } else {
9584
31.7k
                    sql += "AND ov.type = '";
9585
31.7k
                    sql += tableNameTypePair.second;
9586
31.7k
                    sql += "' ";
9587
31.7k
                }
9588
37.6k
            }
9589
105k
            if (deprecated) {
9590
0
                sql += "AND ov.deprecated = 1 ";
9591
0
            }
9592
105k
            if (!approximateMatch) {
9593
84.7k
                sql += "AND a.alt_name = ? COLLATE NOCASE ";
9594
84.7k
                params.push_back(searchedNameWithoutDeprecated);
9595
84.7k
            }
9596
105k
            if (d->hasAuthorityRestriction()) {
9597
24.1k
                sql += "AND ov.auth_name = ? ";
9598
24.1k
                params.emplace_back(d->authority());
9599
24.1k
            }
9600
105k
        }
9601
105k
    }
9602
9603
57.0k
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
9604
57.0k
    if (limitResultCount > 0 &&
9605
32.8k
        limitResultCount <
9606
32.8k
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
9607
32.8k
        !approximateMatch) {
9608
29.1k
        sql += " LIMIT ";
9609
29.1k
        sql += toString(static_cast<int>(limitResultCount));
9610
29.1k
    }
9611
9612
57.0k
    std::list<PairObjectName> res;
9613
57.0k
    std::set<std::pair<std::string, std::string>> setIdentified;
9614
9615
    // Querying geodetic datum is a super hot path when importing from WKT1
9616
    // so cache results.
9617
57.0k
    if (allowedObjectTypes.size() == 1 &&
9618
54.0k
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
9619
15.1k
        approximateMatch && d->authority().empty()) {
9620
145
        auto &mapCanonicalizeGRFName =
9621
145
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
9622
145
        if (mapCanonicalizeGRFName.empty()) {
9623
87
            auto sqlRes = d->run(sql, params);
9624
217k
            for (const auto &row : sqlRes) {
9625
217k
                const auto &name = row[3];
9626
217k
                const auto &deprecatedStr = row[4];
9627
217k
                const auto canonicalizedName(
9628
217k
                    metadata::Identifier::canonicalizeName(name));
9629
217k
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
9630
217k
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
9631
209k
                    v.push_back(row);
9632
209k
                }
9633
217k
            }
9634
87
        }
9635
145
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
9636
145
        if (iter != mapCanonicalizeGRFName.end()) {
9637
16
            const auto &listOfRow = iter->second;
9638
16
            for (const auto &row : listOfRow) {
9639
16
                const auto &auth_name = row[1];
9640
16
                const auto &code = row[2];
9641
16
                auto key = std::pair<std::string, std::string>(auth_name, code);
9642
16
                if (setIdentified.find(key) != setIdentified.end()) {
9643
0
                    continue;
9644
0
                }
9645
16
                setIdentified.insert(std::move(key));
9646
16
                auto factory = d->createFactory(auth_name);
9647
16
                const auto &name = row[3];
9648
16
                res.emplace_back(
9649
16
                    PairObjectName(factory->createGeodeticDatum(code), name));
9650
16
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9651
16
                    break;
9652
16
                }
9653
16
            }
9654
129
        } else {
9655
176k
            for (const auto &pair : mapCanonicalizeGRFName) {
9656
176k
                const auto &listOfRow = pair.second;
9657
193k
                for (const auto &row : listOfRow) {
9658
193k
                    const auto &name = row[3];
9659
193k
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9660
193k
                                 std::string::npos;
9661
193k
                    if (!match) {
9662
193k
                        const auto &canonicalizedName(pair.first);
9663
193k
                        match = ci_find(canonicalizedName,
9664
193k
                                        canonicalizedSearchedName) !=
9665
193k
                                std::string::npos;
9666
193k
                    }
9667
193k
                    if (!match) {
9668
193k
                        continue;
9669
193k
                    }
9670
9671
63
                    const auto &auth_name = row[1];
9672
63
                    const auto &code = row[2];
9673
63
                    auto key =
9674
63
                        std::pair<std::string, std::string>(auth_name, code);
9675
63
                    if (setIdentified.find(key) != setIdentified.end()) {
9676
0
                        continue;
9677
0
                    }
9678
63
                    setIdentified.insert(std::move(key));
9679
63
                    auto factory = d->createFactory(auth_name);
9680
63
                    res.emplace_back(PairObjectName(
9681
63
                        factory->createGeodeticDatum(code), name));
9682
63
                    if (limitResultCount > 0 &&
9683
63
                        res.size() == limitResultCount) {
9684
63
                        break;
9685
63
                    }
9686
63
                }
9687
176k
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9688
63
                    break;
9689
63
                }
9690
176k
            }
9691
129
        }
9692
56.8k
    } else {
9693
56.8k
        auto sqlRes = d->run(sql, params);
9694
56.8k
        bool isFirst = true;
9695
56.8k
        bool firstIsDeprecated = false;
9696
56.8k
        size_t countExactMatch = 0;
9697
56.8k
        size_t countExactMatchOnAlias = 0;
9698
56.8k
        std::size_t hashCodeFirstMatch = 0;
9699
40.8M
        for (const auto &row : sqlRes) {
9700
40.8M
            const auto &name = row[3];
9701
40.8M
            if (approximateMatch) {
9702
40.8M
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9703
40.8M
                             std::string::npos;
9704
40.8M
                if (!match) {
9705
40.8M
                    const auto canonicalizedName(
9706
40.8M
                        metadata::Identifier::canonicalizeName(name));
9707
40.8M
                    match =
9708
40.8M
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
9709
40.8M
                        std::string::npos;
9710
40.8M
                }
9711
40.8M
                if (!match) {
9712
40.8M
                    continue;
9713
40.8M
                }
9714
40.8M
            }
9715
31.2k
            const auto &table_name = row[0];
9716
31.2k
            const auto &auth_name = row[1];
9717
31.2k
            const auto &code = row[2];
9718
31.2k
            auto key = std::pair<std::string, std::string>(auth_name, code);
9719
31.2k
            if (setIdentified.find(key) != setIdentified.end()) {
9720
776
                continue;
9721
776
            }
9722
30.4k
            setIdentified.insert(std::move(key));
9723
30.4k
            const auto &deprecatedStr = row[4];
9724
30.4k
            if (isFirst) {
9725
20.3k
                firstIsDeprecated = deprecatedStr == "1";
9726
20.3k
                isFirst = false;
9727
20.3k
            }
9728
30.4k
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
9729
84
                break;
9730
84
            }
9731
30.3k
            auto factory = d->createFactory(auth_name);
9732
30.3k
            auto getObject = [&factory, datumEnsembleAllowed](
9733
30.3k
                                 const std::string &l_table_name,
9734
30.3k
                                 const std::string &l_code)
9735
30.3k
                -> common::IdentifiedObjectNNPtr {
9736
30.3k
                if (l_table_name == "prime_meridian") {
9737
0
                    return factory->createPrimeMeridian(l_code);
9738
30.3k
                } else if (l_table_name == "ellipsoid") {
9739
256
                    return factory->createEllipsoid(l_code);
9740
30.1k
                } else if (l_table_name == "geodetic_datum") {
9741
374
                    if (datumEnsembleAllowed) {
9742
374
                        datum::GeodeticReferenceFramePtr datum;
9743
374
                        datum::DatumEnsemblePtr datumEnsemble;
9744
374
                        constexpr bool turnEnsembleAsDatum = false;
9745
374
                        factory->createGeodeticDatumOrEnsemble(
9746
374
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9747
374
                        if (datum) {
9748
374
                            return NN_NO_CHECK(datum);
9749
374
                        }
9750
374
                        assert(datumEnsemble);
9751
0
                        return NN_NO_CHECK(datumEnsemble);
9752
374
                    }
9753
0
                    return factory->createGeodeticDatum(l_code);
9754
29.7k
                } else if (l_table_name == "vertical_datum") {
9755
168
                    if (datumEnsembleAllowed) {
9756
168
                        datum::VerticalReferenceFramePtr datum;
9757
168
                        datum::DatumEnsemblePtr datumEnsemble;
9758
168
                        constexpr bool turnEnsembleAsDatum = false;
9759
168
                        factory->createVerticalDatumOrEnsemble(
9760
168
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9761
168
                        if (datum) {
9762
163
                            return NN_NO_CHECK(datum);
9763
163
                        }
9764
168
                        assert(datumEnsemble);
9765
5
                        return NN_NO_CHECK(datumEnsemble);
9766
168
                    }
9767
0
                    return factory->createVerticalDatum(l_code);
9768
29.5k
                } else if (l_table_name == "engineering_datum") {
9769
7
                    return factory->createEngineeringDatum(l_code);
9770
29.5k
                } else if (l_table_name == "geodetic_crs") {
9771
23.4k
                    return factory->createGeodeticCRS(l_code);
9772
23.4k
                } else if (l_table_name == "projected_crs") {
9773
3.53k
                    return factory->createProjectedCRS(l_code);
9774
3.53k
                } else if (l_table_name == "derived_projected_crs") {
9775
0
                    return factory->createDerivedProjectedCRS(l_code);
9776
2.55k
                } else if (l_table_name == "vertical_crs") {
9777
914
                    return factory->createVerticalCRS(l_code);
9778
1.64k
                } else if (l_table_name == "compound_crs") {
9779
125
                    return factory->createCompoundCRS(l_code);
9780
1.51k
                } else if (l_table_name == "engineering_crs") {
9781
19
                    return factory->createEngineeringCRS(l_code);
9782
1.50k
                } else if (l_table_name == "conversion") {
9783
484
                    return factory->createConversion(l_code);
9784
1.01k
                } else if (l_table_name == "grid_transformation" ||
9785
765
                           l_table_name == "helmert_transformation" ||
9786
275
                           l_table_name == "other_transformation" ||
9787
1.01k
                           l_table_name == "concatenated_operation") {
9788
1.01k
                    return factory->createCoordinateOperation(l_code, true);
9789
1.01k
                }
9790
0
                throw std::runtime_error("Unsupported table_name");
9791
30.3k
            };
9792
30.3k
            const auto obj = getObject(table_name, code);
9793
30.3k
            if (metadata::Identifier::isEquivalentName(
9794
30.3k
                    obj->nameStr().c_str(), searchedName.c_str(), false)) {
9795
18.9k
                countExactMatch++;
9796
18.9k
            } else if (metadata::Identifier::isEquivalentName(
9797
11.3k
                           name.c_str(), searchedName.c_str(), false)) {
9798
29
                countExactMatchOnAlias++;
9799
29
            }
9800
9801
30.3k
            const auto objPtr = obj.get();
9802
30.3k
            if (res.empty()) {
9803
20.3k
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9804
20.3k
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9805
8.10k
                hashCodeFirstMatch = 0;
9806
8.10k
            }
9807
9808
30.3k
            res.emplace_back(PairObjectName(obj, name));
9809
30.3k
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9810
1.13k
                break;
9811
1.13k
            }
9812
30.3k
        }
9813
9814
        // If we found several objects that are an exact match, and all objects
9815
        // have the same type, and we are not in approximate mode, only keep the
9816
        // objects with the exact name match.
9817
56.8k
        if ((countExactMatch + countExactMatchOnAlias) >= 1 &&
9818
18.9k
            hashCodeFirstMatch != 0 && !approximateMatch) {
9819
18.9k
            std::list<PairObjectName> resTmp;
9820
18.9k
            bool biggerDifferencesAllowed = (countExactMatch == 0);
9821
18.9k
            for (const auto &pair : res) {
9822
18.9k
                if (metadata::Identifier::isEquivalentName(
9823
18.9k
                        pair.first->nameStr().c_str(), searchedName.c_str(),
9824
18.9k
                        biggerDifferencesAllowed) ||
9825
16
                    (countExactMatch == 0 &&
9826
16
                     metadata::Identifier::isEquivalentName(
9827
16
                         pair.second.c_str(), searchedName.c_str(),
9828
18.9k
                         biggerDifferencesAllowed))) {
9829
18.9k
                    resTmp.emplace_back(pair);
9830
18.9k
                }
9831
18.9k
            }
9832
18.9k
            res = std::move(resTmp);
9833
18.9k
        }
9834
56.8k
    }
9835
9836
57.0k
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9837
18.8k
        const auto &aName = a.first->nameStr();
9838
18.8k
        const auto &bName = b.first->nameStr();
9839
9840
18.8k
        if (aName.size() < bName.size()) {
9841
569
            return true;
9842
569
        }
9843
18.2k
        if (aName.size() > bName.size()) {
9844
9.48k
            return false;
9845
9.48k
        }
9846
9847
8.81k
        const auto &aIds = a.first->identifiers();
9848
8.81k
        const auto &bIds = b.first->identifiers();
9849
8.81k
        if (aIds.size() < bIds.size()) {
9850
0
            return true;
9851
0
        }
9852
8.81k
        if (aIds.size() > bIds.size()) {
9853
0
            return false;
9854
0
        }
9855
8.81k
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9856
8.81k
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9857
8.81k
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9858
8.81k
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9859
8.81k
            if (codeSpaceComparison < 0) {
9860
875
                return true;
9861
875
            }
9862
7.93k
            if (codeSpaceComparison > 0) {
9863
1.04k
                return false;
9864
1.04k
            }
9865
6.89k
            const auto &aCode = aIds[idx]->code();
9866
6.89k
            const auto &bCode = bIds[idx]->code();
9867
6.89k
            const auto codeComparison = aCode.compare(bCode);
9868
6.89k
            if (codeComparison < 0) {
9869
2.02k
                return true;
9870
2.02k
            }
9871
4.86k
            if (codeComparison > 0) {
9872
4.86k
                return false;
9873
4.86k
            }
9874
4.86k
        }
9875
0
        return strcmp(typeid(a.first.get()).name(),
9876
0
                      typeid(b.first.get()).name()) < 0;
9877
8.81k
    };
9878
9879
57.0k
    res.sort(sortLambda);
9880
9881
57.0k
    return res;
9882
59.0k
}
9883
//! @endcond
9884
9885
// ---------------------------------------------------------------------------
9886
9887
/** \brief Return a list of area of use from their name
9888
 *
9889
 * @param name Searched name.
9890
 * @param approximateMatch Whether approximate name identification is allowed.
9891
 * @return list of (auth_name, code) of matched objects.
9892
 * @throw FactoryException in case of error.
9893
 */
9894
std::list<std::pair<std::string, std::string>>
9895
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9896
0
                                        bool approximateMatch) const {
9897
0
    std::string sql(
9898
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9899
0
    ListOfParams params;
9900
0
    if (d->hasAuthorityRestriction()) {
9901
0
        sql += " auth_name = ? AND ";
9902
0
        params.emplace_back(d->authority());
9903
0
    }
9904
0
    sql += "name LIKE ?";
9905
0
    if (!approximateMatch) {
9906
0
        params.push_back(name);
9907
0
    } else {
9908
0
        params.push_back('%' + name + '%');
9909
0
    }
9910
0
    auto sqlRes = d->run(sql, params);
9911
0
    std::list<std::pair<std::string, std::string>> res;
9912
0
    for (const auto &row : sqlRes) {
9913
0
        res.emplace_back(row[0], row[1]);
9914
0
    }
9915
0
    return res;
9916
0
}
9917
9918
// ---------------------------------------------------------------------------
9919
9920
//! @cond Doxygen_Suppress
9921
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9922
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9923
0
    std::string sql(
9924
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9925
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9926
0
        "((semi_minor_axis IS NOT NULL AND "
9927
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9928
0
        "((inv_flattening IS NOT NULL AND "
9929
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9930
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9931
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9932
0
                        ellipsoid->computedInverseFlattening()};
9933
0
    auto sqlRes = d->run(sql, params);
9934
0
    std::list<datum::EllipsoidNNPtr> res;
9935
0
    for (const auto &row : sqlRes) {
9936
0
        const auto &auth_name = row[0];
9937
0
        const auto &code = row[1];
9938
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9939
0
    }
9940
0
    return res;
9941
0
}
9942
//! @endcond
9943
9944
// ---------------------------------------------------------------------------
9945
9946
//! @cond Doxygen_Suppress
9947
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9948
    const std::string &datum_auth_name, const std::string &datum_code,
9949
21.5k
    const std::string &geodetic_crs_type) const {
9950
21.5k
    std::string sql(
9951
21.5k
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9952
21.5k
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9953
21.5k
    ListOfParams params{datum_auth_name, datum_code};
9954
21.5k
    if (d->hasAuthorityRestriction()) {
9955
3.06k
        sql += " AND auth_name = ?";
9956
3.06k
        params.emplace_back(d->authority());
9957
3.06k
    }
9958
21.5k
    if (!geodetic_crs_type.empty()) {
9959
2.33k
        sql += " AND type = ?";
9960
2.33k
        params.emplace_back(geodetic_crs_type);
9961
2.33k
    }
9962
21.5k
    sql += " ORDER BY auth_name, code";
9963
21.5k
    auto sqlRes = d->run(sql, params);
9964
21.5k
    std::list<crs::GeodeticCRSNNPtr> res;
9965
100k
    for (const auto &row : sqlRes) {
9966
100k
        const auto &auth_name = row[0];
9967
100k
        const auto &code = row[1];
9968
100k
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9969
100k
    }
9970
21.5k
    return res;
9971
21.5k
}
9972
//! @endcond
9973
9974
// ---------------------------------------------------------------------------
9975
9976
//! @cond Doxygen_Suppress
9977
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9978
    const datum::GeodeticReferenceFrameNNPtr &datum,
9979
    const std::string &preferredAuthName,
9980
36.5k
    const std::string &geodetic_crs_type) const {
9981
36.5k
    std::list<crs::GeodeticCRSNNPtr> candidates;
9982
36.5k
    const auto &ids = datum->identifiers();
9983
36.5k
    const auto &datumName = datum->nameStr();
9984
36.5k
    if (!ids.empty()) {
9985
21.5k
        for (const auto &id : ids) {
9986
21.5k
            const auto &authName = *(id->codeSpace());
9987
21.5k
            const auto &code = id->code();
9988
21.5k
            if (!authName.empty()) {
9989
21.5k
                const auto tmpFactory =
9990
21.5k
                    (preferredAuthName == authName)
9991
21.5k
                        ? create(databaseContext(), authName)
9992
21.5k
                        : NN_NO_CHECK(d->getSharedFromThis());
9993
21.5k
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9994
21.5k
                    authName, code, geodetic_crs_type);
9995
100k
                for (const auto &candidate : l_candidates) {
9996
100k
                    candidates.emplace_back(candidate);
9997
100k
                }
9998
21.5k
            }
9999
21.5k
        }
10000
21.5k
    } else if (datumName != "unknown" && datumName != "unnamed") {
10001
15.0k
        auto matches = createObjectsFromName(
10002
15.0k
            datumName,
10003
15.0k
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
10004
15.0k
            2);
10005
15.0k
        if (matches.size() == 1) {
10006
0
            const auto &match = matches.front();
10007
0
            if (datum->_isEquivalentTo(match.get(),
10008
0
                                       util::IComparable::Criterion::EQUIVALENT,
10009
0
                                       databaseContext().as_nullable()) &&
10010
0
                !match->identifiers().empty()) {
10011
0
                return createGeodeticCRSFromDatum(
10012
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
10013
0
                        match),
10014
0
                    preferredAuthName, geodetic_crs_type);
10015
0
            }
10016
0
        }
10017
15.0k
    }
10018
36.5k
    return candidates;
10019
36.5k
}
10020
//! @endcond
10021
10022
// ---------------------------------------------------------------------------
10023
10024
//! @cond Doxygen_Suppress
10025
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
10026
117
    const std::string &datum_auth_name, const std::string &datum_code) const {
10027
117
    std::string sql(
10028
117
        "SELECT auth_name, code FROM vertical_crs WHERE "
10029
117
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
10030
117
    ListOfParams params{datum_auth_name, datum_code};
10031
117
    if (d->hasAuthorityRestriction()) {
10032
0
        sql += " AND auth_name = ?";
10033
0
        params.emplace_back(d->authority());
10034
0
    }
10035
117
    auto sqlRes = d->run(sql, params);
10036
117
    std::list<crs::VerticalCRSNNPtr> res;
10037
117
    for (const auto &row : sqlRes) {
10038
103
        const auto &auth_name = row[0];
10039
103
        const auto &code = row[1];
10040
103
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
10041
103
    }
10042
117
    return res;
10043
117
}
10044
//! @endcond
10045
10046
// ---------------------------------------------------------------------------
10047
10048
//! @cond Doxygen_Suppress
10049
std::list<crs::GeodeticCRSNNPtr>
10050
AuthorityFactory::createGeodeticCRSFromEllipsoid(
10051
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
10052
0
    const std::string &geodetic_crs_type) const {
10053
0
    std::string sql(
10054
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
10055
0
        "JOIN geodetic_datum ON "
10056
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
10057
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
10058
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
10059
0
        "geodetic_datum.ellipsoid_code = ? AND "
10060
0
        "geodetic_datum.deprecated = 0 AND "
10061
0
        "geodetic_crs.deprecated = 0");
10062
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
10063
0
    if (d->hasAuthorityRestriction()) {
10064
0
        sql += " AND geodetic_crs.auth_name = ?";
10065
0
        params.emplace_back(d->authority());
10066
0
    }
10067
0
    if (!geodetic_crs_type.empty()) {
10068
0
        sql += " AND geodetic_crs.type = ?";
10069
0
        params.emplace_back(geodetic_crs_type);
10070
0
    }
10071
0
    auto sqlRes = d->run(sql, params);
10072
0
    std::list<crs::GeodeticCRSNNPtr> res;
10073
0
    for (const auto &row : sqlRes) {
10074
0
        const auto &auth_name = row[0];
10075
0
        const auto &code = row[1];
10076
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
10077
0
    }
10078
0
    return res;
10079
0
}
10080
//! @endcond
10081
10082
// ---------------------------------------------------------------------------
10083
10084
//! @cond Doxygen_Suppress
10085
static std::string buildSqlLookForAuthNameCode(
10086
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
10087
0
    const char *prefixField) {
10088
0
    std::string sql("(");
10089
10090
0
    std::set<std::string> authorities;
10091
0
    for (const auto &crs : list) {
10092
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10093
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10094
0
                                   : crs.first->identifiers();
10095
0
        if (!ids.empty()) {
10096
0
            authorities.insert(*(ids[0]->codeSpace()));
10097
0
        }
10098
0
    }
10099
0
    bool firstAuth = true;
10100
0
    for (const auto &auth_name : authorities) {
10101
0
        if (!firstAuth) {
10102
0
            sql += " OR ";
10103
0
        }
10104
0
        firstAuth = false;
10105
0
        sql += "( ";
10106
0
        sql += prefixField;
10107
0
        sql += "auth_name = ? AND ";
10108
0
        sql += prefixField;
10109
0
        sql += "code IN (";
10110
0
        params.emplace_back(auth_name);
10111
0
        bool firstGeodCRSForAuth = true;
10112
0
        for (const auto &crs : list) {
10113
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10114
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10115
0
                                       : crs.first->identifiers();
10116
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
10117
0
                if (!firstGeodCRSForAuth) {
10118
0
                    sql += ',';
10119
0
                }
10120
0
                firstGeodCRSForAuth = false;
10121
0
                sql += '?';
10122
0
                params.emplace_back(ids[0]->code());
10123
0
            }
10124
0
        }
10125
0
        sql += "))";
10126
0
    }
10127
0
    sql += ')';
10128
0
    return sql;
10129
0
}
10130
//! @endcond
10131
10132
// ---------------------------------------------------------------------------
10133
10134
//! @cond Doxygen_Suppress
10135
std::list<crs::ProjectedCRSNNPtr>
10136
AuthorityFactory::createProjectedCRSFromExisting(
10137
0
    const crs::ProjectedCRSNNPtr &crs) const {
10138
0
    std::list<crs::ProjectedCRSNNPtr> res;
10139
10140
0
    const auto &conv = crs->derivingConversionRef();
10141
0
    const auto &method = conv->method();
10142
0
    const auto methodEPSGCode = method->getEPSGCode();
10143
0
    if (methodEPSGCode == 0) {
10144
0
        return res;
10145
0
    }
10146
10147
0
    auto lockedThisFactory(d->getSharedFromThis());
10148
0
    assert(lockedThisFactory);
10149
0
    const auto &baseCRS(crs->baseCRS());
10150
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
10151
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
10152
0
    if (geogCRS) {
10153
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
10154
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
10155
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
10156
0
            const auto &unit =
10157
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
10158
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
10159
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
10160
0
                                        geogCRS->nameStr()),
10161
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
10162
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
10163
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
10164
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
10165
0
            auto otherCandidatesGeodCRS =
10166
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
10167
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
10168
0
                                     otherCandidatesGeodCRS.begin(),
10169
0
                                     otherCandidatesGeodCRS.end());
10170
0
        }
10171
0
    }
10172
10173
0
    std::string sql(
10174
0
        "SELECT projected_crs.auth_name, projected_crs.code, "
10175
0
        "projected_crs.name FROM projected_crs "
10176
0
        "JOIN conversion_table conv ON "
10177
0
        "projected_crs.conversion_auth_name = conv.auth_name AND "
10178
0
        "projected_crs.conversion_code = conv.code "
10179
0
        "JOIN geodetic_crs gcrs ON "
10180
0
        "gcrs.auth_name = projected_crs.geodetic_crs_auth_name AND "
10181
0
        "gcrs.code = projected_crs.geodetic_crs_code "
10182
0
        "JOIN geodetic_datum datum ON "
10183
0
        "datum.auth_name = gcrs.datum_auth_name AND "
10184
0
        "datum.code = gcrs.datum_code "
10185
0
        "JOIN ellipsoid ellps ON "
10186
0
        "ellps.auth_name = datum.ellipsoid_auth_name AND "
10187
0
        "ellps.code = datum.ellipsoid_code "
10188
0
        "WHERE "
10189
0
        "abs(ellps.semi_major_axis - ?) <= 1e-4 * ellps.semi_major_axis AND "
10190
0
        "projected_crs.deprecated = 0 AND ");
10191
0
    ListOfParams params;
10192
0
    params.emplace_back(
10193
0
        toString(crs->baseCRS()->ellipsoid()->semiMajorAxis().value()));
10194
0
    if (!candidatesGeodCRS.empty()) {
10195
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10196
0
                                           "projected_crs.geodetic_crs_");
10197
0
        sql += " AND ";
10198
0
    }
10199
0
    sql += "conv.method_auth_name = 'EPSG' AND "
10200
0
           "conv.method_code = ?";
10201
0
    params.emplace_back(toString(methodEPSGCode));
10202
0
    if (d->hasAuthorityRestriction()) {
10203
0
        sql += " AND projected_crs.auth_name = ?";
10204
0
        params.emplace_back(d->authority());
10205
0
    }
10206
10207
0
    int iParam = 0;
10208
0
    bool hasLat1stStd = false;
10209
0
    double lat1stStd = 0;
10210
0
    int iParamLat1stStd = 0;
10211
0
    bool hasLat2ndStd = false;
10212
0
    double lat2ndStd = 0;
10213
0
    int iParamLat2ndStd = 0;
10214
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
10215
0
        iParam++;
10216
0
        auto opParamvalue =
10217
0
            dynamic_cast<const operation::OperationParameterValue *>(
10218
0
                genOpParamvalue.get());
10219
0
        if (!opParamvalue) {
10220
0
            break;
10221
0
        }
10222
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
10223
0
        const auto &parameterValue = opParamvalue->parameterValue();
10224
0
        if (!(paramEPSGCode > 0 &&
10225
0
              parameterValue->type() ==
10226
0
                  operation::ParameterValue::Type::MEASURE)) {
10227
0
            break;
10228
0
        }
10229
0
        const auto &measure = parameterValue->value();
10230
0
        const auto &unit = measure.unit();
10231
0
        if (unit == common::UnitOfMeasure::DEGREE &&
10232
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
10233
0
            if (methodEPSGCode ==
10234
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
10235
                // Special case for standard parallels of LCC_2SP. See below
10236
0
                if (paramEPSGCode ==
10237
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
10238
0
                    hasLat1stStd = true;
10239
0
                    lat1stStd = measure.value();
10240
0
                    iParamLat1stStd = iParam;
10241
0
                    continue;
10242
0
                } else if (paramEPSGCode ==
10243
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
10244
0
                    hasLat2ndStd = true;
10245
0
                    lat2ndStd = measure.value();
10246
0
                    iParamLat2ndStd = iParam;
10247
0
                    continue;
10248
0
                }
10249
0
            }
10250
0
            const auto iParamAsStr(toString(iParam));
10251
0
            sql += " AND conv.param";
10252
0
            sql += iParamAsStr;
10253
0
            sql += "_code = ? AND conv.param";
10254
0
            sql += iParamAsStr;
10255
0
            sql += "_auth_name = 'EPSG' AND conv.param";
10256
0
            sql += iParamAsStr;
10257
0
            sql += "_value BETWEEN ? AND ?";
10258
            // As angles might be expressed with the odd unit EPSG:9110
10259
            // "sexagesimal DMS", we have to provide a broad range
10260
0
            params.emplace_back(toString(paramEPSGCode));
10261
0
            params.emplace_back(measure.value() - 1);
10262
0
            params.emplace_back(measure.value() + 1);
10263
0
        }
10264
0
    }
10265
10266
    // Special case for standard parallels of LCC_2SP: they can be switched
10267
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
10268
0
        hasLat1stStd && hasLat2ndStd) {
10269
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
10270
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
10271
0
        sql += " AND conv.param";
10272
0
        sql += iParam1AsStr;
10273
0
        sql += "_code = ? AND conv.param";
10274
0
        sql += iParam1AsStr;
10275
0
        sql += "_auth_name = 'EPSG' AND conv.param";
10276
0
        sql += iParam2AsStr;
10277
0
        sql += "_code = ? AND conv.param";
10278
0
        sql += iParam2AsStr;
10279
0
        sql += "_auth_name = 'EPSG' AND ((";
10280
0
        params.emplace_back(
10281
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
10282
0
        params.emplace_back(
10283
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
10284
0
        double val1 = lat1stStd;
10285
0
        double val2 = lat2ndStd;
10286
0
        for (int i = 0; i < 2; i++) {
10287
0
            if (i == 1) {
10288
0
                sql += ") OR (";
10289
0
                std::swap(val1, val2);
10290
0
            }
10291
0
            sql += "conv.param";
10292
0
            sql += iParam1AsStr;
10293
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
10294
0
            sql += iParam2AsStr;
10295
0
            sql += "_value BETWEEN ? AND ?";
10296
0
            params.emplace_back(val1 - 1);
10297
0
            params.emplace_back(val1 + 1);
10298
0
            params.emplace_back(val2 - 1);
10299
0
            params.emplace_back(val2 + 1);
10300
0
        }
10301
0
        sql += "))";
10302
0
    }
10303
0
    auto sqlRes = d->run(sql, params);
10304
10305
0
    for (const auto &row : sqlRes) {
10306
0
        const auto &name = row[2];
10307
0
        if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(),
10308
0
                                                   name.c_str())) {
10309
0
            const auto &auth_name = row[0];
10310
0
            const auto &code = row[1];
10311
0
            res.emplace_back(
10312
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10313
0
        }
10314
0
    }
10315
0
    if (!res.empty()) {
10316
0
        return res;
10317
0
    }
10318
10319
0
    params.clear();
10320
10321
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
10322
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
10323
0
    if (!candidatesGeodCRS.empty()) {
10324
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10325
0
                                           "geodetic_crs_");
10326
0
        sql += " AND ";
10327
0
    }
10328
10329
0
    const auto escapeLikeStr = [](const std::string &str) {
10330
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
10331
0
                          "%", "\\%");
10332
0
    };
10333
10334
0
    const auto ellpsSemiMajorStr =
10335
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
10336
10337
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
10338
10339
    // WKT2 definition
10340
0
    {
10341
0
        std::string patternVal("%");
10342
10343
0
        patternVal += ',';
10344
0
        patternVal += ellpsSemiMajorStr;
10345
0
        patternVal += '%';
10346
10347
0
        patternVal += escapeLikeStr(method->nameStr());
10348
0
        patternVal += '%';
10349
10350
0
        params.emplace_back(patternVal);
10351
0
    }
10352
10353
0
    const auto *mapping = getMapping(method.get());
10354
0
    if (mapping && mapping->proj_name_main) {
10355
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
10356
10357
0
        std::string patternVal("%");
10358
0
        patternVal += "proj=";
10359
0
        patternVal += mapping->proj_name_main;
10360
0
        patternVal += '%';
10361
0
        params.emplace_back(patternVal);
10362
10363
        // could be a= or R=
10364
0
        patternVal = "%=";
10365
0
        patternVal += ellpsSemiMajorStr;
10366
0
        patternVal += '%';
10367
0
        params.emplace_back(patternVal);
10368
10369
0
        std::string projEllpsName;
10370
0
        std::string ellpsName;
10371
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
10372
0
                                                            ellpsName)) {
10373
0
            sql += " OR text_definition LIKE ?";
10374
            // Could be ellps= or datum=
10375
0
            patternVal = "%=";
10376
0
            patternVal += projEllpsName;
10377
0
            patternVal += '%';
10378
0
            params.emplace_back(patternVal);
10379
0
        }
10380
10381
0
        sql += "))";
10382
0
    }
10383
10384
    // WKT1_GDAL definition
10385
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
10386
0
    if (wkt1GDALMethodName) {
10387
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10388
0
        std::string patternVal("%");
10389
10390
0
        patternVal += ',';
10391
0
        patternVal += ellpsSemiMajorStr;
10392
0
        patternVal += '%';
10393
10394
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
10395
0
        patternVal += '%';
10396
10397
0
        params.emplace_back(patternVal);
10398
0
    }
10399
10400
    // WKT1_ESRI definition
10401
0
    const char *esriMethodName = conv->getESRIMethodName();
10402
0
    if (esriMethodName) {
10403
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10404
0
        std::string patternVal("%");
10405
10406
0
        patternVal += ',';
10407
0
        patternVal += ellpsSemiMajorStr;
10408
0
        patternVal += '%';
10409
10410
0
        patternVal += escapeLikeStr(esriMethodName);
10411
0
        patternVal += '%';
10412
10413
0
        auto fe =
10414
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
10415
0
        if (*fe == Measure()) {
10416
0
            fe = &conv->parameterValueMeasure(
10417
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
10418
0
        }
10419
0
        if (!(*fe == Measure())) {
10420
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
10421
0
            patternVal +=
10422
0
                toString(fe->convertToUnit(
10423
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
10424
0
                         10);
10425
0
            patternVal += '%';
10426
0
        }
10427
10428
0
        auto lat = &conv->parameterValueMeasure(
10429
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
10430
0
        if (*lat == Measure()) {
10431
0
            lat = &conv->parameterValueMeasure(
10432
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
10433
0
        }
10434
0
        if (!(*lat == Measure())) {
10435
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
10436
0
            const auto &angularUnit =
10437
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
10438
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
10439
0
                    : UnitOfMeasure::DEGREE;
10440
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
10441
0
            patternVal += '%';
10442
0
        }
10443
10444
0
        params.emplace_back(patternVal);
10445
0
    }
10446
0
    sql += ")";
10447
0
    if (d->hasAuthorityRestriction()) {
10448
0
        sql += " AND auth_name = ?";
10449
0
        params.emplace_back(d->authority());
10450
0
    }
10451
10452
0
    auto sqlRes2 = d->run(sql, params);
10453
10454
0
    if (sqlRes.size() <= 200) {
10455
0
        for (const auto &row : sqlRes) {
10456
0
            const auto &auth_name = row[0];
10457
0
            const auto &code = row[1];
10458
0
            res.emplace_back(
10459
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10460
0
        }
10461
0
    }
10462
0
    if (sqlRes2.size() <= 200) {
10463
0
        for (const auto &row : sqlRes2) {
10464
0
            const auto &auth_name = row[0];
10465
0
            const auto &code = row[1];
10466
0
            res.emplace_back(
10467
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10468
0
        }
10469
0
    }
10470
10471
0
    return res;
10472
0
}
10473
10474
// ---------------------------------------------------------------------------
10475
10476
std::list<crs::CompoundCRSNNPtr>
10477
AuthorityFactory::createCompoundCRSFromExisting(
10478
0
    const crs::CompoundCRSNNPtr &crs) const {
10479
0
    std::list<crs::CompoundCRSNNPtr> res;
10480
10481
0
    auto lockedThisFactory(d->getSharedFromThis());
10482
0
    assert(lockedThisFactory);
10483
10484
0
    const auto &components = crs->componentReferenceSystems();
10485
0
    if (components.size() != 2) {
10486
0
        return res;
10487
0
    }
10488
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
10489
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
10490
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
10491
0
        return res;
10492
0
    }
10493
10494
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
10495
0
                    "deprecated = 0 AND ");
10496
0
    ListOfParams params;
10497
0
    bool addAnd = false;
10498
0
    if (!candidatesHorizCRS.empty()) {
10499
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
10500
0
                                           "horiz_crs_");
10501
0
        addAnd = true;
10502
0
    }
10503
0
    if (!candidatesVertCRS.empty()) {
10504
0
        if (addAnd) {
10505
0
            sql += " AND ";
10506
0
        }
10507
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
10508
0
                                           "vertical_crs_");
10509
0
        addAnd = true;
10510
0
    }
10511
0
    if (d->hasAuthorityRestriction()) {
10512
0
        if (addAnd) {
10513
0
            sql += " AND ";
10514
0
        }
10515
0
        sql += "auth_name = ?";
10516
0
        params.emplace_back(d->authority());
10517
0
    }
10518
10519
0
    auto sqlRes = d->run(sql, params);
10520
0
    for (const auto &row : sqlRes) {
10521
0
        const auto &auth_name = row[0];
10522
0
        const auto &code = row[1];
10523
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
10524
0
    }
10525
0
    return res;
10526
0
}
10527
10528
// ---------------------------------------------------------------------------
10529
10530
std::vector<operation::CoordinateOperationNNPtr>
10531
AuthorityFactory::getTransformationsForGeoid(
10532
296
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
10533
296
    std::vector<operation::CoordinateOperationNNPtr> res;
10534
10535
296
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
10536
296
                          "geoid_model WHERE name = ?");
10537
296
    auto sqlRes = d->run(sql, {geoidName});
10538
296
    for (const auto &row : sqlRes) {
10539
0
        const auto &auth_name = row[0];
10540
0
        const auto &code = row[1];
10541
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
10542
0
            code, usePROJAlternativeGridNames));
10543
0
    }
10544
10545
296
    return res;
10546
296
}
10547
10548
// ---------------------------------------------------------------------------
10549
10550
std::vector<operation::PointMotionOperationNNPtr>
10551
AuthorityFactory::getPointMotionOperationsFor(
10552
8
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
10553
8
    std::vector<operation::PointMotionOperationNNPtr> res;
10554
8
    const auto crsList =
10555
8
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
10556
8
                                   /* preferredAuthName = */ std::string(),
10557
8
                                   /* geodetic_crs_type = */ std::string());
10558
8
    if (crsList.empty())
10559
0
        return res;
10560
8
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
10561
8
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
10562
8
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
10563
8
                    "(");
10564
8
    bool addOr = false;
10565
8
    ListOfParams params;
10566
17
    for (const auto &candidateCrs : crsList) {
10567
17
        if (addOr)
10568
9
            sql += " OR ";
10569
17
        addOr = true;
10570
17
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
10571
17
        const auto &ids = candidateCrs->identifiers();
10572
17
        params.emplace_back(*(ids[0]->codeSpace()));
10573
17
        params.emplace_back(ids[0]->code());
10574
17
    }
10575
8
    sql += ")";
10576
8
    if (d->hasAuthorityRestriction()) {
10577
0
        sql += " AND auth_name = ?";
10578
0
        params.emplace_back(d->authority());
10579
0
    }
10580
10581
8
    auto sqlRes = d->run(sql, params);
10582
8
    for (const auto &row : sqlRes) {
10583
0
        const auto &auth_name = row[0];
10584
0
        const auto &code = row[1];
10585
0
        auto pmo =
10586
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
10587
0
                d->createFactory(auth_name)->createCoordinateOperation(
10588
0
                    code, usePROJAlternativeGridNames));
10589
0
        if (pmo) {
10590
0
            res.emplace_back(NN_NO_CHECK(pmo));
10591
0
        }
10592
0
    }
10593
8
    return res;
10594
8
}
10595
10596
//! @endcond
10597
10598
// ---------------------------------------------------------------------------
10599
10600
//! @cond Doxygen_Suppress
10601
1.50k
FactoryException::FactoryException(const char *message) : Exception(message) {}
10602
10603
// ---------------------------------------------------------------------------
10604
10605
FactoryException::FactoryException(const std::string &message)
10606
615
    : Exception(message) {}
10607
10608
// ---------------------------------------------------------------------------
10609
10610
2.11k
FactoryException::~FactoryException() = default;
10611
10612
// ---------------------------------------------------------------------------
10613
10614
0
FactoryException::FactoryException(const FactoryException &) = default;
10615
//! @endcond
10616
10617
// ---------------------------------------------------------------------------
10618
10619
//! @cond Doxygen_Suppress
10620
10621
struct NoSuchAuthorityCodeException::Private {
10622
    std::string authority_;
10623
    std::string code_;
10624
10625
    Private(const std::string &authority, const std::string &code)
10626
615
        : authority_(authority), code_(code) {}
10627
};
10628
10629
// ---------------------------------------------------------------------------
10630
10631
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10632
    const std::string &message, const std::string &authority,
10633
    const std::string &code)
10634
615
    : FactoryException(message), d(std::make_unique<Private>(authority, code)) {
10635
615
}
10636
10637
// ---------------------------------------------------------------------------
10638
10639
615
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
10640
10641
// ---------------------------------------------------------------------------
10642
10643
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10644
    const NoSuchAuthorityCodeException &other)
10645
0
    : FactoryException(other), d(std::make_unique<Private>(*(other.d))) {}
10646
//! @endcond
10647
10648
// ---------------------------------------------------------------------------
10649
10650
/** \brief Returns authority name. */
10651
78
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
10652
78
    return d->authority_;
10653
78
}
10654
10655
// ---------------------------------------------------------------------------
10656
10657
/** \brief Returns authority code. */
10658
78
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
10659
78
    return d->code_;
10660
78
}
10661
10662
// ---------------------------------------------------------------------------
10663
10664
} // namespace io
10665
NS_PROJ_END
10666
10667
// ---------------------------------------------------------------------------
10668
10669
11.4k
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }