Coverage Report

Created: 2026-05-30 06:46

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.08M
    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
47.9k
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
145
146
3.48M
    const Type &type() const { return type_; }
147
148
    // cppcheck-suppress functionStatic
149
3.30M
    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
182k
    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
2.21M
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
173
2.21M
    switch (sqlite3_value_type(val)) {
174
2.21M
    case SQLITE_FLOAT:
175
2.21M
        gotVal = true;
176
2.21M
        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
2.21M
    }
186
2.21M
}
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
759
        east_lon += 360.0;
205
759
    }
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
250k
                                        int /* argc */, sqlite3_value **argv) {
217
250k
    bool b0, b1, b2, b3, b4, b5, b6, b7;
218
250k
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
219
250k
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
220
250k
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
221
250k
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
222
250k
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
223
250k
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
224
250k
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
225
250k
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
226
250k
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
227
0
        sqlite3_result_null(pContext);
228
0
        return;
229
0
    }
230
250k
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
231
250k
                                                         east_lon1, north_lat1);
232
250k
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
233
250k
                                                         east_lon2, north_lat2);
234
250k
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
235
250k
}
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.89k
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
260
9.89k
        assert(sqlite_handle_);
261
9.89k
    }
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.89k
    const std::string &path() const { return path_; }
274
275
81.6k
    sqlite3 *handle() { return sqlite_handle_; }
276
277
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
278
1.19M
    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.89k
SQLiteHandle::~SQLiteHandle() {
309
9.89k
    if (close_handle_) {
310
9.89k
        sqlite3_close(sqlite_handle_);
311
9.89k
    }
312
9.89k
}
313
314
// ---------------------------------------------------------------------------
315
316
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
317
9.89k
                                                 const std::string &pathIn) {
318
319
9.89k
    std::string path(pathIn);
320
9.89k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
321
    // Minimum version for correct performance: 3.11
322
9.89k
    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.89k
    std::string vfsName;
329
9.89k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
330
9.89k
    std::unique_ptr<SQLite3VFS> vfs;
331
9.89k
#endif
332
333
9.89k
#ifdef EMBED_RESOURCE_FILES
334
9.89k
    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.89k
#endif
355
356
9.89k
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
357
9.89k
        if (ctx->custom_sqlite3_vfs_name.empty()) {
358
9.89k
        vfs = SQLite3VFS::create(false, true, true);
359
9.89k
        if (vfs == nullptr) {
360
0
            throw FactoryException("Open of " + path + " failed");
361
0
        }
362
9.89k
        vfsName = vfs->name();
363
9.89k
    } else
364
0
#endif
365
0
    {
366
0
        vfsName = ctx->custom_sqlite3_vfs_name;
367
0
    }
368
9.89k
    sqlite3 *sqlite_handle = nullptr;
369
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
370
9.89k
    if (sqlite3_open_v2(
371
9.89k
            path.c_str(), &sqlite_handle,
372
9.89k
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
373
9.89k
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
374
9.89k
        !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.89k
    auto handle =
381
9.89k
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
382
9.89k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
383
9.89k
    handle->vfs_ = std::move(vfs);
384
9.89k
#endif
385
9.89k
    handle->initialize();
386
9.89k
    handle->path_ = path;
387
9.89k
    handle->checkDatabaseLayout(path, path, std::string());
388
9.89k
    return handle;
389
9.89k
}
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.20M
                               bool useMaxFloatPrecision) {
421
1.20M
    int nBindField = 1;
422
3.48M
    for (const auto &param : parameters) {
423
3.48M
        const auto &paramType = param.type();
424
3.48M
        if (paramType == SQLValues::Type::STRING) {
425
3.30M
            const auto &strValue = param.stringValue();
426
3.30M
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
427
3.30M
                              static_cast<int>(strValue.size()),
428
3.30M
                              SQLITE_TRANSIENT);
429
3.30M
        } else if (paramType == SQLValues::Type::INT) {
430
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
431
182k
        } else {
432
182k
            assert(paramType == SQLValues::Type::DOUBLE);
433
182k
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
434
182k
        }
435
3.48M
        nBindField++;
436
3.48M
    }
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.20M
    SQLResultSet result;
461
1.20M
    const int column_count = sqlite3_column_count(stmt);
462
65.8M
    while (true) {
463
65.8M
        int ret = sqlite3_step(stmt);
464
65.8M
        if (ret == SQLITE_ROW) {
465
64.6M
            SQLRow row(column_count);
466
458M
            for (int i = 0; i < column_count; i++) {
467
393M
                if (useMaxFloatPrecision &&
468
62.1k
                    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
393M
                } else {
476
393M
                    const char *txt = reinterpret_cast<const char *>(
477
393M
                        sqlite3_column_text(stmt, i));
478
393M
                    if (txt) {
479
390M
                        row[i] = txt;
480
390M
                    }
481
393M
                }
482
393M
            }
483
64.6M
            result.emplace_back(std::move(row));
484
64.6M
        } else if (ret == SQLITE_DONE) {
485
1.20M
            break;
486
1.20M
        } 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
65.8M
    }
496
1.20M
    return result;
497
1.20M
}
498
499
// ---------------------------------------------------------------------------
500
501
SQLResultSet SQLiteHandle::run(const std::string &sql,
502
                               const ListOfParams &parameters,
503
9.89k
                               bool useMaxFloatPrecision) {
504
9.89k
    sqlite3_stmt *stmt = nullptr;
505
9.89k
    try {
506
9.89k
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
507
9.89k
                               static_cast<int>(sql.size()), &stmt,
508
9.89k
                               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.89k
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
515
9.89k
        sqlite3_finalize(stmt);
516
9.89k
        return ret;
517
9.89k
    } catch (const std::exception &) {
518
0
        if (stmt)
519
0
            sqlite3_finalize(stmt);
520
0
        throw;
521
0
    }
522
9.89k
}
523
524
// ---------------------------------------------------------------------------
525
526
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
527
                                       const std::string &path,
528
9.89k
                                       const std::string &dbNamePrefix) {
529
9.89k
    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.89k
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
536
9.89k
                   "metadata WHERE key IN "
537
9.89k
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
538
9.89k
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
539
9.89k
    if (res.empty() && !dbNamePrefix.empty()) {
540
        // Accept auxiliary databases without layout metadata.
541
0
        return;
542
0
    }
543
9.89k
    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.89k
    int major = 0;
550
9.89k
    int minor = 0;
551
19.7k
    for (const auto &row : res) {
552
19.7k
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
553
9.89k
            major = atoi(row[1].c_str());
554
9.89k
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
555
9.89k
            minor = atoi(row[1].c_str());
556
9.89k
        }
557
19.7k
    }
558
9.89k
    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.89k
    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.89k
    if (dbNamePrefix.empty()) {
577
9.89k
        nLayoutVersionMajor_ = major;
578
9.89k
        nLayoutVersionMinor_ = minor;
579
9.89k
    } 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.89k
}
589
590
// ---------------------------------------------------------------------------
591
592
#ifndef SQLITE_DETERMINISTIC
593
#define SQLITE_DETERMINISTIC 0
594
#endif
595
596
9.89k
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.89k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
603
9.89k
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
604
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
605
0
                             0x100000);
606
0
    }
607
608
9.89k
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
609
9.89k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
610
9.89k
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
611
9.89k
                            nullptr);
612
613
9.89k
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
614
9.89k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
615
9.89k
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
616
9.89k
}
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.3k
SQLiteHandleCache &SQLiteHandleCache::get() {
646
    // Global cache
647
21.3k
    static SQLiteHandleCache gSQLiteHandleCache;
648
21.3k
    return gSQLiteHandleCache;
649
21.3k
}
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.89k
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
662
9.89k
    std::lock_guard<std::mutex> lock(sMutex_);
663
664
9.89k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
665
9.89k
    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.89k
#endif
683
684
9.89k
    std::shared_ptr<SQLiteHandle> handle;
685
9.89k
    std::string key = path + ctx->custom_sqlite3_vfs_name;
686
9.89k
    if (!cache_.tryGet(key, handle)) {
687
9.89k
        handle = SQLiteHandle::open(ctx, path);
688
9.89k
        cache_.insert(key, handle);
689
9.89k
    }
690
9.89k
    return handle;
691
9.89k
}
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
247k
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
718
9.89k
    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
318
            : dbContext_(context) {
738
318
            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
318
            ++dbContext_->getPrivate()->recLevel_;
744
318
        }
745
746
318
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
747
748
      private:
749
        DatabaseContextNNPtr dbContext_;
750
    };
751
752
151
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
753
151
        return mapCanonicalizeGRFName_;
754
151
    }
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.89k
DatabaseContext::Private::Private() = default;
987
988
// ---------------------------------------------------------------------------
989
990
9.89k
DatabaseContext::Private::~Private() {
991
9.89k
    assert(recLevel_ == 0);
992
993
9.89k
    closeDB();
994
9.89k
}
995
996
// ---------------------------------------------------------------------------
997
998
9.89k
void DatabaseContext::Private::closeDB() noexcept {
999
1000
9.89k
    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.6k
    for (auto &pair : mapSqlToStatement_) {
1020
81.6k
        sqlite3_finalize(pair.second);
1021
81.6k
    }
1022
9.89k
    mapSqlToStatement_.clear();
1023
1024
9.89k
    sqlite_handle_.reset();
1025
9.89k
}
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.19M
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
1049
1.19M
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
1050
1.19M
    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.19M
#endif
1058
1.19M
    return sqlite_handle_;
1059
1.19M
}
1060
1061
// ---------------------------------------------------------------------------
1062
1063
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1064
                                               const std::string &code,
1065
135k
                                               const util::BaseObjectPtr &obj) {
1066
135k
    cache.insert(code, obj);
1067
135k
}
1068
1069
// ---------------------------------------------------------------------------
1070
1071
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1072
                                            const std::string &code,
1073
1.74M
                                            util::BaseObjectPtr &obj) {
1074
1.74M
    cache.tryGet(code, obj);
1075
1.74M
}
1076
1077
// ---------------------------------------------------------------------------
1078
1079
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1080
    const std::string &code,
1081
192k
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1082
192k
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1083
192k
}
1084
1085
// ---------------------------------------------------------------------------
1086
1087
void DatabaseContext::Private::cache(
1088
    const std::string &code,
1089
40.7k
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1090
40.7k
    cacheCRSToCrsCoordOp_.insert(code, list);
1091
40.7k
}
1092
1093
// ---------------------------------------------------------------------------
1094
1095
762k
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1096
762k
    util::BaseObjectPtr obj;
1097
762k
    getFromCache(cacheCRS_, code, obj);
1098
762k
    return std::static_pointer_cast<crs::CRS>(obj);
1099
762k
}
1100
1101
// ---------------------------------------------------------------------------
1102
1103
void DatabaseContext::Private::cache(const std::string &code,
1104
46.7k
                                     const crs::CRSNNPtr &crs) {
1105
46.7k
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1106
46.7k
}
1107
1108
// ---------------------------------------------------------------------------
1109
1110
common::UnitOfMeasurePtr
1111
114k
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1112
114k
    util::BaseObjectPtr obj;
1113
114k
    getFromCache(cacheUOM_, code, obj);
1114
114k
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1115
114k
}
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
355k
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1128
355k
    util::BaseObjectPtr obj;
1129
355k
    getFromCache(cacheGeodeticDatum_, code, obj);
1130
355k
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1131
355k
}
1132
1133
// ---------------------------------------------------------------------------
1134
1135
void DatabaseContext::Private::cache(
1136
42.7k
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1137
42.7k
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1138
42.7k
}
1139
1140
// ---------------------------------------------------------------------------
1141
1142
datum::DatumEnsemblePtr
1143
379k
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1144
379k
    util::BaseObjectPtr obj;
1145
379k
    getFromCache(cacheDatumEnsemble_, code, obj);
1146
379k
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1147
379k
}
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.0k
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1160
43.0k
    util::BaseObjectPtr obj;
1161
43.0k
    getFromCache(cacheEllipsoid_, code, obj);
1162
43.0k
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1163
43.0k
}
1164
1165
// ---------------------------------------------------------------------------
1166
1167
void DatabaseContext::Private::cache(const std::string &code,
1168
7.34k
                                     const datum::EllipsoidNNPtr &ellps) {
1169
7.34k
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1170
7.34k
}
1171
1172
// ---------------------------------------------------------------------------
1173
1174
datum::PrimeMeridianPtr
1175
42.7k
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1176
42.7k
    util::BaseObjectPtr obj;
1177
42.7k
    getFromCache(cachePrimeMeridian_, code, obj);
1178
42.7k
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1179
42.7k
}
1180
1181
// ---------------------------------------------------------------------------
1182
1183
void DatabaseContext::Private::cache(const std::string &code,
1184
4.10k
                                     const datum::PrimeMeridianNNPtr &pm) {
1185
4.10k
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1186
4.10k
}
1187
1188
// ---------------------------------------------------------------------------
1189
1190
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1191
46.3k
    const std::string &code) {
1192
46.3k
    util::BaseObjectPtr obj;
1193
46.3k
    getFromCache(cacheCS_, code, obj);
1194
46.3k
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1195
46.3k
}
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
143k
                                                    GridInfoCache &info) {
1224
143k
    return cacheGridInfo_.tryGet(code, info);
1225
143k
}
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.9k
                                     const GridInfoCache &info) {
1237
40.9k
    cacheGridInfo_.insert(code, info);
1238
40.9k
}
1239
1240
// ---------------------------------------------------------------------------
1241
1242
void DatabaseContext::Private::open(const std::string &databasePath,
1243
9.89k
                                    PJ_CONTEXT *ctx) {
1244
9.89k
    if (!ctx) {
1245
0
        ctx = pj_get_default_ctx();
1246
0
    }
1247
1248
9.89k
    setPjCtxt(ctx);
1249
9.89k
    std::string path(databasePath);
1250
9.89k
    if (path.empty()) {
1251
9.89k
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
1252
9.89k
        path.resize(2048);
1253
9.89k
        const bool found =
1254
9.89k
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
1255
9.89k
        path.resize(strlen(path.c_str()));
1256
9.89k
        if (!found)
1257
0
#endif
1258
0
        {
1259
0
#ifdef EMBED_RESOURCE_FILES
1260
0
            path = EMBEDDED_PROJ_DB;
1261
#else
1262
            throw FactoryException("Cannot find proj.db");
1263
#endif
1264
0
        }
1265
9.89k
    }
1266
1267
9.89k
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1268
1269
9.89k
    databasePath_ = sqlite_handle_->path();
1270
9.89k
}
1271
1272
// ---------------------------------------------------------------------------
1273
1274
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1275
1276
0
    assert(sqlite_handle);
1277
0
    assert(!sqlite_handle_);
1278
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1279
0
}
1280
1281
// ---------------------------------------------------------------------------
1282
1283
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1284
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1285
0
                                           memoryDbForInsertPath_.empty()
1286
0
                                       ? ""
1287
0
                                       : "db_0.");
1288
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1289
0
                        "sqlite_master WHERE type = ");
1290
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1291
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1292
0
    std::vector<std::string> res;
1293
0
    for (const auto &objectType : objectTypes) {
1294
0
        const auto sqlRes = run(sqlBegin + objectType);
1295
0
        for (const auto &row : sqlRes) {
1296
0
            res.emplace_back(row[0]);
1297
0
        }
1298
0
    }
1299
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1300
0
        res.emplace_back(
1301
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1302
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1303
0
        res.emplace_back(
1304
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1305
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1306
0
    }
1307
0
    return res;
1308
0
}
1309
1310
// ---------------------------------------------------------------------------
1311
1312
void DatabaseContext::Private::attachExtraDatabases(
1313
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1314
1315
0
    auto l_handle = handle();
1316
0
    assert(l_handle);
1317
1318
0
    auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN "
1319
0
                      "('table', 'view') "
1320
0
                      "AND name NOT LIKE 'sqlite_stat%'");
1321
1322
0
    struct TableStructure {
1323
0
        std::string name{};
1324
0
        bool isTable = false;
1325
0
        std::string sql{};
1326
0
        std::vector<std::string> columns{};
1327
0
    };
1328
0
    std::vector<TableStructure> tablesStructure;
1329
0
    for (const auto &rowTable : tables) {
1330
0
        TableStructure tableStructure;
1331
0
        tableStructure.name = rowTable[0];
1332
0
        tableStructure.isTable = rowTable[1] == "table";
1333
0
        tableStructure.sql = rowTable[2];
1334
0
        auto tableInfo =
1335
0
            run("PRAGMA table_info(\"" +
1336
0
                replaceAll(tableStructure.name, "\"", "\"\"") + "\")");
1337
0
        for (const auto &rowCol : tableInfo) {
1338
0
            const auto &colName = rowCol[1];
1339
0
            tableStructure.columns.push_back(colName);
1340
0
        }
1341
0
        tablesStructure.push_back(std::move(tableStructure));
1342
0
    }
1343
1344
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1345
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1346
1347
0
    closeDB();
1348
0
    if (auxiliaryDatabasePaths.empty()) {
1349
0
        open(databasePath_, pjCtxt());
1350
0
        return;
1351
0
    }
1352
1353
0
    sqlite3 *sqlite_handle = nullptr;
1354
0
    sqlite3_open_v2(
1355
0
        ":memory:", &sqlite_handle,
1356
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1357
0
    if (!sqlite_handle) {
1358
0
        throw FactoryException("cannot create in memory database");
1359
0
    }
1360
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1361
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1362
0
    l_handle = sqlite_handle_;
1363
1364
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1365
0
    detach_ = true;
1366
0
    int count = 1;
1367
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1368
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1369
0
        std::string sql = "ATTACH DATABASE ? AS ";
1370
0
        sql += attachedDbName;
1371
0
        count++;
1372
0
        run(sql, {otherDbPath});
1373
1374
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1375
0
                                      attachedDbName + '.');
1376
0
    }
1377
1378
0
    for (const auto &tableStructure : tablesStructure) {
1379
0
        if (tableStructure.isTable) {
1380
0
            std::string sql("CREATE TEMP VIEW ");
1381
0
            sql += tableStructure.name;
1382
0
            sql += " AS ";
1383
0
            for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1384
0
                std::string selectFromAux("SELECT ");
1385
0
                bool firstCol = true;
1386
0
                for (const auto &colName : tableStructure.columns) {
1387
0
                    if (!firstCol) {
1388
0
                        selectFromAux += ", ";
1389
0
                    }
1390
0
                    firstCol = false;
1391
0
                    selectFromAux += colName;
1392
0
                }
1393
0
                selectFromAux += " FROM db_";
1394
0
                selectFromAux += toString(static_cast<int>(i));
1395
0
                selectFromAux += ".";
1396
0
                selectFromAux += tableStructure.name;
1397
1398
0
                try {
1399
                    // Check that the request will succeed. In case of 'sparse'
1400
                    // databases...
1401
0
                    run(selectFromAux + " LIMIT 0");
1402
1403
0
                    if (i > 0) {
1404
0
                        if (tableStructure.name == "conversion_method")
1405
0
                            sql += " UNION ";
1406
0
                        else
1407
0
                            sql += " UNION ALL ";
1408
0
                    }
1409
0
                    sql += selectFromAux;
1410
0
                } catch (const std::exception &) {
1411
0
                }
1412
0
            }
1413
0
            run(sql);
1414
0
        } else {
1415
0
            run(replaceAll(tableStructure.sql, "CREATE VIEW",
1416
0
                           "CREATE TEMP VIEW"));
1417
0
        }
1418
0
    }
1419
0
}
1420
1421
// ---------------------------------------------------------------------------
1422
1423
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1424
                                           const ListOfParams &parameters,
1425
1.19M
                                           bool useMaxFloatPrecision) {
1426
1427
1.19M
    auto l_handle = handle();
1428
1.19M
    assert(l_handle);
1429
1430
1.19M
    sqlite3_stmt *stmt = nullptr;
1431
1.19M
    auto iter = mapSqlToStatement_.find(sql);
1432
1.19M
    if (iter != mapSqlToStatement_.end()) {
1433
1.10M
        stmt = iter->second;
1434
1.10M
        sqlite3_reset(stmt);
1435
1.10M
    } else {
1436
81.6k
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1437
81.6k
                               static_cast<int>(sql.size()), &stmt,
1438
81.6k
                               nullptr) != SQLITE_OK) {
1439
0
            throw FactoryException(
1440
0
                std::string("SQLite error [ ")
1441
0
                    .append(sqlite3_errmsg(l_handle->handle()))
1442
0
                    .append(" ] on ")
1443
0
                    .append(sql));
1444
0
        }
1445
81.6k
        mapSqlToStatement_.insert(
1446
81.6k
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1447
81.6k
    }
1448
1449
1.19M
    ++queryCounter_;
1450
1451
1.19M
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1452
1.19M
}
1453
1454
// ---------------------------------------------------------------------------
1455
1456
0
static std::string formatStatement(const char *fmt, ...) {
1457
0
    std::string res;
1458
0
    va_list args;
1459
0
    va_start(args, fmt);
1460
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1461
0
        if (fmt[i] == '%') {
1462
0
            if (fmt[i + 1] == '%') {
1463
0
                res += '%';
1464
0
            } else if (fmt[i + 1] == 'q') {
1465
0
                const char *arg = va_arg(args, const char *);
1466
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1467
0
                    if (arg[j] == '\'')
1468
0
                        res += arg[j];
1469
0
                    res += arg[j];
1470
0
                }
1471
0
            } else if (fmt[i + 1] == 'Q') {
1472
0
                const char *arg = va_arg(args, const char *);
1473
0
                if (arg == nullptr)
1474
0
                    res += "NULL";
1475
0
                else {
1476
0
                    res += '\'';
1477
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1478
0
                        if (arg[j] == '\'')
1479
0
                            res += arg[j];
1480
0
                        res += arg[j];
1481
0
                    }
1482
0
                    res += '\'';
1483
0
                }
1484
0
            } else if (fmt[i + 1] == 's') {
1485
0
                const char *arg = va_arg(args, const char *);
1486
0
                res += arg;
1487
0
            } else if (fmt[i + 1] == 'f') {
1488
0
                const double arg = va_arg(args, double);
1489
0
                res += toString(arg);
1490
0
            } else if (fmt[i + 1] == 'd') {
1491
0
                const int arg = va_arg(args, int);
1492
0
                res += toString(arg);
1493
0
            } else {
1494
0
                va_end(args);
1495
0
                throw FactoryException(
1496
0
                    "Unsupported formatter in formatStatement()");
1497
0
            }
1498
0
            ++i;
1499
0
        } else {
1500
0
            res += fmt[i];
1501
0
        }
1502
0
    }
1503
0
    va_end(args);
1504
0
    return res;
1505
0
}
1506
1507
// ---------------------------------------------------------------------------
1508
1509
void DatabaseContext::Private::appendSql(
1510
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1511
0
    sqlStatements.emplace_back(sql);
1512
0
    char *errMsg = nullptr;
1513
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1514
0
                     &errMsg) != SQLITE_OK) {
1515
0
        std::string s("Cannot execute " + sql);
1516
0
        if (errMsg) {
1517
0
            s += " : ";
1518
0
            s += errMsg;
1519
0
        }
1520
0
        sqlite3_free(errMsg);
1521
0
        throw FactoryException(s);
1522
0
    }
1523
0
    sqlite3_free(errMsg);
1524
0
}
1525
1526
// ---------------------------------------------------------------------------
1527
1528
static void identifyFromNameOrCode(
1529
    const DatabaseContextNNPtr &dbContext,
1530
    const std::vector<std::string> &allowedAuthorities,
1531
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1532
    std::function<std::shared_ptr<util::IComparable>(
1533
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1534
        instantiateFunc,
1535
    AuthorityFactory::ObjectType objType, std::string &authName,
1536
0
    std::string &code) {
1537
1538
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1539
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1540
1541
0
    for (const auto &id : obj->identifiers()) {
1542
0
        try {
1543
0
            const auto &idAuthName = *(id->codeSpace());
1544
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1545
0
                          allowedAuthoritiesTmp.end(),
1546
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1547
0
                const auto factory =
1548
0
                    AuthorityFactory::create(dbContext, idAuthName);
1549
0
                if (instantiateFunc(factory, id->code())
1550
0
                        ->isEquivalentTo(
1551
0
                            obj.get(),
1552
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1553
0
                    authName = idAuthName;
1554
0
                    code = id->code();
1555
0
                    return;
1556
0
                }
1557
0
            }
1558
0
        } catch (const std::exception &) {
1559
0
        }
1560
0
    }
1561
1562
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1563
0
        const auto factory =
1564
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1565
0
        const auto candidates =
1566
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1567
0
        for (const auto &candidate : candidates) {
1568
0
            const auto &ids = candidate->identifiers();
1569
0
            if (!ids.empty() &&
1570
0
                candidate->isEquivalentTo(
1571
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1572
0
                const auto &id = ids.front();
1573
0
                authName = *(id->codeSpace());
1574
0
                code = id->code();
1575
0
                return;
1576
0
            }
1577
0
        }
1578
0
    }
1579
0
}
1580
1581
// ---------------------------------------------------------------------------
1582
1583
static void
1584
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1585
                       const std::vector<std::string> &allowedAuthorities,
1586
                       const std::string &authNameParent,
1587
                       const datum::DatumEnsembleNNPtr &obj,
1588
0
                       std::string &authName, std::string &code) {
1589
0
    const char *type = "geodetic_datum";
1590
0
    if (!obj->datums().empty() &&
1591
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1592
0
            obj->datums().front().get())) {
1593
0
        type = "vertical_datum";
1594
0
    }
1595
0
    const auto instantiateFunc =
1596
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1597
0
                const std::string &lCode) {
1598
0
            return util::nn_static_pointer_cast<util::IComparable>(
1599
0
                authFactory->createDatumEnsemble(lCode, type));
1600
0
        };
1601
0
    identifyFromNameOrCode(
1602
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1603
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1604
0
}
1605
1606
// ---------------------------------------------------------------------------
1607
1608
static void
1609
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1610
                       const std::vector<std::string> &allowedAuthorities,
1611
                       const std::string &authNameParent,
1612
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1613
0
                       std::string &authName, std::string &code) {
1614
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1615
0
                                    const std::string &lCode) {
1616
0
        return util::nn_static_pointer_cast<util::IComparable>(
1617
0
            authFactory->createGeodeticDatum(lCode));
1618
0
    };
1619
0
    identifyFromNameOrCode(
1620
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1621
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1622
0
}
1623
1624
// ---------------------------------------------------------------------------
1625
1626
static void
1627
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1628
                       const std::vector<std::string> &allowedAuthorities,
1629
                       const std::string &authNameParent,
1630
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1631
0
                       std::string &code) {
1632
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1633
0
                                    const std::string &lCode) {
1634
0
        return util::nn_static_pointer_cast<util::IComparable>(
1635
0
            authFactory->createEllipsoid(lCode));
1636
0
    };
1637
0
    identifyFromNameOrCode(
1638
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1639
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1640
0
}
1641
1642
// ---------------------------------------------------------------------------
1643
1644
static void
1645
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1646
                       const std::vector<std::string> &allowedAuthorities,
1647
                       const std::string &authNameParent,
1648
                       const datum::PrimeMeridianNNPtr &obj,
1649
0
                       std::string &authName, std::string &code) {
1650
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1651
0
                                    const std::string &lCode) {
1652
0
        return util::nn_static_pointer_cast<util::IComparable>(
1653
0
            authFactory->createPrimeMeridian(lCode));
1654
0
    };
1655
0
    identifyFromNameOrCode(
1656
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1657
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1658
0
}
1659
1660
// ---------------------------------------------------------------------------
1661
1662
static void
1663
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1664
                       const std::vector<std::string> &allowedAuthorities,
1665
                       const std::string &authNameParent,
1666
                       const datum::VerticalReferenceFrameNNPtr &obj,
1667
0
                       std::string &authName, std::string &code) {
1668
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1669
0
                                    const std::string &lCode) {
1670
0
        return util::nn_static_pointer_cast<util::IComparable>(
1671
0
            authFactory->createVerticalDatum(lCode));
1672
0
    };
1673
0
    identifyFromNameOrCode(
1674
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1675
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1676
0
}
1677
1678
// ---------------------------------------------------------------------------
1679
1680
static void
1681
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1682
                       const std::vector<std::string> &allowedAuthorities,
1683
                       const std::string &authNameParent,
1684
                       const datum::DatumNNPtr &obj, std::string &authName,
1685
0
                       std::string &code) {
1686
0
    if (const auto geodeticDatum =
1687
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1688
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1689
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1690
0
    } else if (const auto verticalDatum =
1691
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1692
0
                       obj)) {
1693
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1694
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1695
0
    } else {
1696
0
        throw FactoryException("Unhandled type of datum");
1697
0
    }
1698
0
}
1699
1700
// ---------------------------------------------------------------------------
1701
1702
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1703
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1704
0
        return CS_TYPE_ELLIPSOIDAL;
1705
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1706
0
        return CS_TYPE_CARTESIAN;
1707
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1708
0
        return CS_TYPE_VERTICAL;
1709
0
    }
1710
0
    return nullptr;
1711
0
}
1712
1713
// ---------------------------------------------------------------------------
1714
1715
std::string
1716
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1717
                                       const std::string &authName,
1718
0
                                       const std::string &codePrototype) {
1719
0
    std::string code(codePrototype);
1720
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1721
0
            {authName, code})
1722
0
            .empty()) {
1723
0
        return code;
1724
0
    }
1725
1726
0
    for (int counter = 2; counter < 10; counter++) {
1727
0
        code = codePrototype + '_' + toString(counter);
1728
0
        if (run("SELECT 1 FROM " + tableName +
1729
0
                    " WHERE auth_name = ? AND code = ?",
1730
0
                {authName, code})
1731
0
                .empty()) {
1732
0
            return code;
1733
0
        }
1734
0
    }
1735
1736
    // shouldn't happen hopefully...
1737
0
    throw FactoryException("Cannot insert " + tableName +
1738
0
                           ": too many similar codes");
1739
0
}
1740
1741
// ---------------------------------------------------------------------------
1742
1743
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1744
0
    switch (unit.type()) {
1745
0
    case common::UnitOfMeasure::Type::LINEAR:
1746
0
        return "length";
1747
1748
0
    case common::UnitOfMeasure::Type::ANGULAR:
1749
0
        return "angle";
1750
1751
0
    case common::UnitOfMeasure::Type::SCALE:
1752
0
        return "scale";
1753
1754
0
    case common::UnitOfMeasure::Type::TIME:
1755
0
        return "time";
1756
1757
0
    default:
1758
0
        break;
1759
0
    }
1760
0
    return nullptr;
1761
0
}
1762
1763
// ---------------------------------------------------------------------------
1764
1765
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1766
                                        const common::UnitOfMeasure &obj,
1767
                                        std::string &authName,
1768
0
                                        std::string &code) {
1769
    // Identify quickly a few well-known units
1770
0
    const double convFactor = obj.conversionToSI();
1771
0
    switch (obj.type()) {
1772
0
    case common::UnitOfMeasure::Type::LINEAR: {
1773
0
        if (convFactor == 1.0) {
1774
0
            authName = metadata::Identifier::EPSG;
1775
0
            code = "9001";
1776
0
            return;
1777
0
        }
1778
0
        break;
1779
0
    }
1780
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1781
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1782
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1783
0
            1e-10 * CONV_FACTOR_DEGREE) {
1784
0
            authName = metadata::Identifier::EPSG;
1785
0
            code = "9102";
1786
0
            return;
1787
0
        }
1788
0
        break;
1789
0
    }
1790
0
    case common::UnitOfMeasure::Type::SCALE: {
1791
0
        if (convFactor == 1.0) {
1792
0
            authName = metadata::Identifier::EPSG;
1793
0
            code = "9201";
1794
0
            return;
1795
0
        }
1796
0
        break;
1797
0
    }
1798
0
    default:
1799
0
        break;
1800
0
    }
1801
1802
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1803
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1804
0
    ListOfParams params{convFactor};
1805
0
    const char *type = getUnitDatabaseType(obj);
1806
0
    if (type) {
1807
0
        sql += " AND type = ?";
1808
0
        params.emplace_back(std::string(type));
1809
0
    }
1810
0
    sql += " ORDER BY auth_name, code";
1811
0
    const auto res = run(sql, params);
1812
0
    for (const auto &row : res) {
1813
0
        const auto &rowAuthName = row[0];
1814
0
        const auto &rowCode = row[1];
1815
0
        const auto tmpAuthFactory =
1816
0
            AuthorityFactory::create(dbContext, rowAuthName);
1817
0
        try {
1818
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1819
0
            authName = rowAuthName;
1820
0
            code = rowCode;
1821
0
            return;
1822
0
        } catch (const std::exception &) {
1823
0
        }
1824
0
    }
1825
0
}
1826
1827
// ---------------------------------------------------------------------------
1828
1829
void DatabaseContext::Private::identifyOrInsert(
1830
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1831
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1832
0
    std::vector<std::string> &sqlStatements) {
1833
0
    authName = unit.codeSpace();
1834
0
    code = unit.code();
1835
0
    if (authName.empty()) {
1836
0
        identify(dbContext, unit, authName, code);
1837
0
    }
1838
0
    if (!authName.empty()) {
1839
0
        return;
1840
0
    }
1841
0
    const char *type = getUnitDatabaseType(unit);
1842
0
    if (type == nullptr) {
1843
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1844
0
    }
1845
1846
    // Insert new record
1847
0
    authName = ownerAuthName;
1848
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1849
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1850
1851
0
    const auto sql = formatStatement(
1852
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1853
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1854
0
        unit.conversionToSI());
1855
0
    appendSql(sqlStatements, sql);
1856
0
}
1857
1858
// ---------------------------------------------------------------------------
1859
1860
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1861
                                        const cs::CoordinateSystemNNPtr &obj,
1862
                                        std::string &authName,
1863
0
                                        std::string &code) {
1864
1865
0
    const auto &axisList = obj->axisList();
1866
0
    if (axisList.size() == 1U &&
1867
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1868
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1869
0
        (axisList[0]->nameStr() == "Up" ||
1870
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1871
        // preferred coordinate system for gravity-related height
1872
0
        authName = metadata::Identifier::EPSG;
1873
0
        code = "6499";
1874
0
        return;
1875
0
    }
1876
1877
0
    std::string sql(
1878
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1879
0
    ListOfParams params{static_cast<int>(axisList.size())};
1880
0
    const char *type = getCSDatabaseType(obj);
1881
0
    if (type) {
1882
0
        sql += " AND type = ?";
1883
0
        params.emplace_back(std::string(type));
1884
0
    }
1885
0
    sql += " ORDER BY auth_name, code";
1886
0
    const auto res = run(sql, params);
1887
0
    for (const auto &row : res) {
1888
0
        const auto &rowAuthName = row[0];
1889
0
        const auto &rowCode = row[1];
1890
0
        const auto tmpAuthFactory =
1891
0
            AuthorityFactory::create(dbContext, rowAuthName);
1892
0
        try {
1893
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1894
0
            if (cs->_isEquivalentTo(obj.get(),
1895
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1896
0
                authName = rowAuthName;
1897
0
                code = rowCode;
1898
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1899
                    // preferred coordinate system for cartesian
1900
                    // Easting, Northing
1901
0
                    return;
1902
0
                }
1903
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1904
                    // preferred coordinate system for geographic lat, long
1905
0
                    return;
1906
0
                }
1907
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1908
                    // preferred coordinate system for geographic lat, long, h
1909
0
                    return;
1910
0
                }
1911
0
            }
1912
0
        } catch (const std::exception &) {
1913
0
        }
1914
0
    }
1915
0
}
1916
1917
// ---------------------------------------------------------------------------
1918
1919
void DatabaseContext::Private::identifyOrInsert(
1920
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1921
    const std::string &ownerType, const std::string &ownerAuthName,
1922
    const std::string &ownerCode, std::string &authName, std::string &code,
1923
0
    std::vector<std::string> &sqlStatements) {
1924
1925
0
    identify(dbContext, obj, authName, code);
1926
0
    if (!authName.empty()) {
1927
0
        return;
1928
0
    }
1929
1930
0
    const char *type = getCSDatabaseType(obj);
1931
0
    if (type == nullptr) {
1932
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1933
0
    }
1934
1935
    // Insert new record in coordinate_system
1936
0
    authName = ownerAuthName;
1937
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1938
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1939
1940
0
    const auto &axisList = obj->axisList();
1941
0
    {
1942
0
        const auto sql = formatStatement(
1943
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1944
0
            authName.c_str(), code.c_str(), type,
1945
0
            static_cast<int>(axisList.size()));
1946
0
        appendSql(sqlStatements, sql);
1947
0
    }
1948
1949
    // Insert new records for the axis
1950
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1951
0
        const auto &axis = axisList[i];
1952
0
        std::string uomAuthName;
1953
0
        std::string uomCode;
1954
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1955
0
                         uomCode, sqlStatements);
1956
0
        const auto sql = formatStatement(
1957
0
            "INSERT INTO axis VALUES("
1958
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1959
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1960
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1961
0
            axis->direction().toString().c_str(), authName.c_str(),
1962
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1963
0
        appendSql(sqlStatements, sql);
1964
0
    }
1965
0
}
1966
1967
// ---------------------------------------------------------------------------
1968
1969
static void
1970
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1971
                          const std::string &authName, std::string &sql,
1972
0
                          ListOfParams &params) {
1973
0
    sql += "auth_name IN (?";
1974
0
    params.emplace_back(authName);
1975
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1976
0
        sql += ",?";
1977
0
        params.emplace_back(allowedAuthority);
1978
0
    }
1979
0
    sql += ')';
1980
0
}
1981
1982
// ---------------------------------------------------------------------------
1983
1984
void DatabaseContext::Private::identifyOrInsertUsages(
1985
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1986
    const std::string &authName, const std::string &code,
1987
    const std::vector<std::string> &allowedAuthorities,
1988
0
    std::vector<std::string> &sqlStatements) {
1989
1990
0
    std::string usageCode("USAGE_");
1991
0
    const std::string upperTableName(toupper(tableName));
1992
0
    if (!starts_with(code, upperTableName)) {
1993
0
        usageCode += upperTableName;
1994
0
        usageCode += '_';
1995
0
    }
1996
0
    usageCode += code;
1997
1998
0
    const auto &domains = obj->domains();
1999
0
    if (domains.empty()) {
2000
0
        const auto sql =
2001
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2002
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
2003
0
                            authName.c_str(), usageCode.c_str(),
2004
0
                            tableName.c_str(), authName.c_str(), code.c_str());
2005
0
        appendSql(sqlStatements, sql);
2006
0
        return;
2007
0
    }
2008
2009
0
    int usageCounter = 1;
2010
0
    for (const auto &domain : domains) {
2011
0
        std::string scopeAuthName;
2012
0
        std::string scopeCode;
2013
0
        const auto &scope = domain->scope();
2014
0
        if (scope.has_value()) {
2015
0
            std::string sql =
2016
0
                "SELECT auth_name, code, "
2017
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2018
0
                "AS order_idx "
2019
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
2020
0
            ListOfParams params{*scope};
2021
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2022
0
                                      params);
2023
0
            sql += " ORDER BY order_idx, auth_name, code";
2024
0
            const auto rows = run(sql, params);
2025
0
            if (!rows.empty()) {
2026
0
                const auto &row = rows.front();
2027
0
                scopeAuthName = row[0];
2028
0
                scopeCode = row[1];
2029
0
            } else {
2030
0
                scopeAuthName = authName;
2031
0
                scopeCode = "SCOPE_";
2032
0
                scopeCode += tableName;
2033
0
                scopeCode += '_';
2034
0
                scopeCode += code;
2035
0
                const auto sqlToInsert = formatStatement(
2036
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
2037
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
2038
0
                appendSql(sqlStatements, sqlToInsert);
2039
0
            }
2040
0
        } else {
2041
0
            scopeAuthName = "PROJ";
2042
0
            scopeCode = "SCOPE_UNKNOWN";
2043
0
        }
2044
2045
0
        std::string extentAuthName("PROJ");
2046
0
        std::string extentCode("EXTENT_UNKNOWN");
2047
0
        const auto &extent = domain->domainOfValidity();
2048
0
        if (extent) {
2049
0
            const auto &geogElts = extent->geographicElements();
2050
0
            if (!geogElts.empty()) {
2051
0
                const auto bbox =
2052
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
2053
0
                        geogElts.front().get());
2054
0
                if (bbox) {
2055
0
                    std::string sql =
2056
0
                        "SELECT auth_name, code, "
2057
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2058
0
                        "AS order_idx "
2059
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
2060
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
2061
0
                        "AND ";
2062
0
                    ListOfParams params{
2063
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
2064
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
2065
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2066
0
                                              params);
2067
0
                    sql += " ORDER BY order_idx, auth_name, code";
2068
0
                    const auto rows = run(sql, params);
2069
0
                    if (!rows.empty()) {
2070
0
                        const auto &row = rows.front();
2071
0
                        extentAuthName = row[0];
2072
0
                        extentCode = row[1];
2073
0
                    } else {
2074
0
                        extentAuthName = authName;
2075
0
                        extentCode = "EXTENT_";
2076
0
                        extentCode += tableName;
2077
0
                        extentCode += '_';
2078
0
                        extentCode += code;
2079
0
                        std::string description(*(extent->description()));
2080
0
                        if (description.empty()) {
2081
0
                            description = "unknown";
2082
0
                        }
2083
0
                        const auto sqlToInsert = formatStatement(
2084
0
                            "INSERT INTO extent "
2085
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
2086
0
                            extentAuthName.c_str(), extentCode.c_str(),
2087
0
                            description.c_str(), description.c_str(),
2088
0
                            bbox->southBoundLatitude(),
2089
0
                            bbox->northBoundLatitude(),
2090
0
                            bbox->westBoundLongitude(),
2091
0
                            bbox->eastBoundLongitude());
2092
0
                        appendSql(sqlStatements, sqlToInsert);
2093
0
                    }
2094
0
                }
2095
0
            }
2096
0
        }
2097
2098
0
        if (domains.size() > 1) {
2099
0
            usageCode += '_';
2100
0
            usageCode += toString(usageCounter);
2101
0
        }
2102
0
        const auto sql = formatStatement(
2103
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2104
0
            "'%q','%q','%q','%q');",
2105
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2106
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2107
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2108
0
        appendSql(sqlStatements, sql);
2109
2110
0
        usageCounter++;
2111
0
    }
2112
0
}
2113
2114
// ---------------------------------------------------------------------------
2115
2116
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2117
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2118
    const std::string &code, bool /*numericCode*/,
2119
0
    const std::vector<std::string> &allowedAuthorities) {
2120
2121
0
    const auto self = NN_NO_CHECK(self_.lock());
2122
2123
    // Check if the object is already known under that code
2124
0
    std::string pmAuthName;
2125
0
    std::string pmCode;
2126
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2127
0
                           pmCode);
2128
0
    if (pmAuthName == authName && pmCode == code) {
2129
0
        return {};
2130
0
    }
2131
2132
0
    std::vector<std::string> sqlStatements;
2133
2134
    // Insert new record in prime_meridian table
2135
0
    std::string uomAuthName;
2136
0
    std::string uomCode;
2137
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2138
0
                     uomCode, sqlStatements);
2139
2140
0
    const auto sql = formatStatement(
2141
0
        "INSERT INTO prime_meridian VALUES("
2142
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2143
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2144
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2145
0
    appendSql(sqlStatements, sql);
2146
2147
0
    return sqlStatements;
2148
0
}
2149
2150
// ---------------------------------------------------------------------------
2151
2152
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2153
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2154
    const std::string &code, bool /*numericCode*/,
2155
0
    const std::vector<std::string> &allowedAuthorities) {
2156
2157
0
    const auto self = NN_NO_CHECK(self_.lock());
2158
2159
    // Check if the object is already known under that code
2160
0
    std::string ellipsoidAuthName;
2161
0
    std::string ellipsoidCode;
2162
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2163
0
                           ellipsoidAuthName, ellipsoidCode);
2164
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2165
0
        return {};
2166
0
    }
2167
2168
0
    std::vector<std::string> sqlStatements;
2169
2170
    // Find or insert celestial body
2171
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2172
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2173
0
    constexpr double tolerance = 0.005;
2174
0
    std::string bodyAuthName;
2175
0
    std::string bodyCode;
2176
0
    auto res = run("SELECT auth_name, code, "
2177
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2178
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2179
0
                   {semiMajorAxisMetre, tolerance});
2180
0
    if (!res.empty()) {
2181
0
        const auto &row = res.front();
2182
0
        bodyAuthName = row[0];
2183
0
        bodyCode = row[1];
2184
0
    } else {
2185
0
        bodyAuthName = authName;
2186
0
        bodyCode = "BODY_" + code;
2187
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2188
0
        const auto sql = formatStatement(
2189
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2190
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2191
0
            semiMajorAxisMetre);
2192
0
        appendSql(sqlStatements, sql);
2193
0
    }
2194
2195
    // Insert new record in ellipsoid table
2196
0
    std::string uomAuthName;
2197
0
    std::string uomCode;
2198
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2199
0
                     sqlStatements);
2200
0
    std::string invFlattening("NULL");
2201
0
    std::string semiMinorAxis("NULL");
2202
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2203
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2204
0
    } else {
2205
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2206
0
    }
2207
2208
0
    const auto sql = formatStatement(
2209
0
        "INSERT INTO ellipsoid VALUES("
2210
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2211
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2212
0
        "", // description
2213
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2214
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2215
0
        semiMinorAxis.c_str());
2216
0
    appendSql(sqlStatements, sql);
2217
2218
0
    return sqlStatements;
2219
0
}
2220
2221
// ---------------------------------------------------------------------------
2222
2223
0
static std::string anchorEpochToStr(double val) {
2224
0
    constexpr int BUF_SIZE = 32;
2225
0
    char szBuffer[BUF_SIZE];
2226
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2227
0
    return szBuffer;
2228
0
}
2229
2230
// ---------------------------------------------------------------------------
2231
2232
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2233
    const datum::GeodeticReferenceFrameNNPtr &datum,
2234
    const std::string &authName, const std::string &code, bool numericCode,
2235
0
    const std::vector<std::string> &allowedAuthorities) {
2236
2237
0
    const auto self = NN_NO_CHECK(self_.lock());
2238
2239
    // Check if the object is already known under that code
2240
0
    std::string datumAuthName;
2241
0
    std::string datumCode;
2242
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2243
0
                           datumAuthName, datumCode);
2244
0
    if (datumAuthName == authName && datumCode == code) {
2245
0
        return {};
2246
0
    }
2247
2248
0
    std::vector<std::string> sqlStatements;
2249
2250
    // Find or insert ellipsoid
2251
0
    std::string ellipsoidAuthName;
2252
0
    std::string ellipsoidCode;
2253
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2254
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2255
0
                           ellipsoidAuthName, ellipsoidCode);
2256
0
    if (ellipsoidAuthName.empty()) {
2257
0
        ellipsoidAuthName = authName;
2258
0
        if (numericCode) {
2259
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2260
0
                                                  ellipsoidAuthName, true);
2261
0
        } else {
2262
0
            ellipsoidCode = "ELLPS_" + code;
2263
0
        }
2264
0
        sqlStatements = self->getInsertStatementsFor(
2265
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2266
0
            allowedAuthorities);
2267
0
    }
2268
2269
    // Find or insert prime meridian
2270
0
    std::string pmAuthName;
2271
0
    std::string pmCode;
2272
0
    const auto &pmOfDatum = datum->primeMeridian();
2273
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2274
0
                           pmAuthName, pmCode);
2275
0
    if (pmAuthName.empty()) {
2276
0
        pmAuthName = authName;
2277
0
        if (numericCode) {
2278
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2279
0
        } else {
2280
0
            pmCode = "PM_" + code;
2281
0
        }
2282
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2283
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2284
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2285
0
                             sqlStatementsTmp.end());
2286
0
    }
2287
2288
    // Insert new record in geodetic_datum table
2289
0
    std::string publicationDate("NULL");
2290
0
    if (datum->publicationDate().has_value()) {
2291
0
        publicationDate = '\'';
2292
0
        publicationDate +=
2293
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2294
0
        publicationDate += '\'';
2295
0
    }
2296
0
    std::string frameReferenceEpoch("NULL");
2297
0
    const auto dynamicDatum =
2298
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2299
0
    if (dynamicDatum) {
2300
0
        frameReferenceEpoch =
2301
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2302
0
    }
2303
0
    const std::string anchor(*(datum->anchorDefinition()));
2304
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2305
0
    const auto sql = formatStatement(
2306
0
        "INSERT INTO geodetic_datum VALUES("
2307
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2308
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2309
0
        "", // description
2310
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2311
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
2312
0
        anchor.empty() ? nullptr : anchor.c_str(),
2313
0
        anchorEpoch.has_value()
2314
0
            ? anchorEpochToStr(
2315
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2316
0
                  .c_str()
2317
0
            : "NULL");
2318
0
    appendSql(sqlStatements, sql);
2319
2320
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2321
0
                           allowedAuthorities, sqlStatements);
2322
2323
0
    return sqlStatements;
2324
0
}
2325
2326
// ---------------------------------------------------------------------------
2327
2328
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2329
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2330
    const std::string &code, bool numericCode,
2331
0
    const std::vector<std::string> &allowedAuthorities) {
2332
0
    const auto self = NN_NO_CHECK(self_.lock());
2333
2334
    // Check if the object is already known under that code
2335
0
    std::string datumAuthName;
2336
0
    std::string datumCode;
2337
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2338
0
                           datumAuthName, datumCode);
2339
0
    if (datumAuthName == authName && datumCode == code) {
2340
0
        return {};
2341
0
    }
2342
2343
0
    std::vector<std::string> sqlStatements;
2344
2345
0
    const auto &members = ensemble->datums();
2346
0
    assert(!members.empty());
2347
2348
0
    int counter = 1;
2349
0
    std::vector<std::pair<std::string, std::string>> membersId;
2350
0
    for (const auto &member : members) {
2351
0
        std::string memberAuthName;
2352
0
        std::string memberCode;
2353
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2354
0
                               memberAuthName, memberCode);
2355
0
        if (memberAuthName.empty()) {
2356
0
            memberAuthName = authName;
2357
0
            if (numericCode) {
2358
0
                memberCode =
2359
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2360
0
            } else {
2361
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2362
0
            }
2363
0
            const auto sqlStatementsTmp =
2364
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2365
0
                                             numericCode, allowedAuthorities);
2366
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2367
0
                                 sqlStatementsTmp.end());
2368
0
        }
2369
2370
0
        membersId.emplace_back(
2371
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2372
2373
0
        ++counter;
2374
0
    }
2375
2376
0
    const bool isGeodetic =
2377
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2378
0
            members.front()) != nullptr;
2379
2380
    // Insert new record in geodetic_datum/vertical_datum table
2381
0
    const double accuracy =
2382
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2383
0
    if (isGeodetic) {
2384
0
        const auto firstDatum =
2385
0
            AuthorityFactory::create(self, membersId.front().first)
2386
0
                ->createGeodeticDatum(membersId.front().second);
2387
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2388
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2389
0
        assert(!ellipsoidIds.empty());
2390
0
        const std::string &ellipsoidAuthName =
2391
0
            *(ellipsoidIds.front()->codeSpace());
2392
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2393
0
        const auto &pm = firstDatum->primeMeridian();
2394
0
        const auto &pmIds = pm->identifiers();
2395
0
        assert(!pmIds.empty());
2396
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2397
0
        const std::string &pmCode = pmIds.front()->code();
2398
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2399
0
        const util::optional<common::Measure> &anchorEpoch =
2400
0
            firstDatum->anchorEpoch();
2401
0
        const auto sql = formatStatement(
2402
0
            "INSERT INTO geodetic_datum VALUES("
2403
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2404
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2405
0
            "", // description
2406
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2407
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2408
0
            anchor.empty() ? nullptr : anchor.c_str(),
2409
0
            anchorEpoch.has_value()
2410
0
                ? anchorEpochToStr(
2411
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2412
0
                      .c_str()
2413
0
                : "NULL");
2414
0
        appendSql(sqlStatements, sql);
2415
0
    } else {
2416
0
        const auto firstDatum =
2417
0
            AuthorityFactory::create(self, membersId.front().first)
2418
0
                ->createVerticalDatum(membersId.front().second);
2419
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2420
0
        const util::optional<common::Measure> &anchorEpoch =
2421
0
            firstDatum->anchorEpoch();
2422
0
        const auto sql = formatStatement(
2423
0
            "INSERT INTO vertical_datum VALUES("
2424
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2425
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2426
0
            "", // description
2427
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2428
0
            anchorEpoch.has_value()
2429
0
                ? anchorEpochToStr(
2430
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2431
0
                      .c_str()
2432
0
                : "NULL");
2433
0
        appendSql(sqlStatements, sql);
2434
0
    }
2435
0
    identifyOrInsertUsages(ensemble,
2436
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2437
0
                           authName, code, allowedAuthorities, sqlStatements);
2438
2439
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2440
0
                                       : "vertical_datum_ensemble_member";
2441
0
    counter = 1;
2442
0
    for (const auto &authCodePair : membersId) {
2443
0
        const auto sql = formatStatement(
2444
0
            "INSERT INTO %s VALUES("
2445
0
            "'%q','%q','%q','%q',%d);",
2446
0
            tableName, authName.c_str(), code.c_str(),
2447
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2448
0
        appendSql(sqlStatements, sql);
2449
0
        ++counter;
2450
0
    }
2451
2452
0
    return sqlStatements;
2453
0
}
2454
2455
// ---------------------------------------------------------------------------
2456
2457
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2458
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2459
    const std::string &code, bool numericCode,
2460
0
    const std::vector<std::string> &allowedAuthorities) {
2461
2462
0
    const auto self = NN_NO_CHECK(self_.lock());
2463
2464
0
    std::vector<std::string> sqlStatements;
2465
2466
    // Find or insert datum/datum ensemble
2467
0
    std::string datumAuthName;
2468
0
    std::string datumCode;
2469
0
    const auto &ensemble = crs->datumEnsemble();
2470
0
    if (ensemble) {
2471
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2472
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2473
0
                               datumAuthName, datumCode);
2474
0
        if (datumAuthName.empty()) {
2475
0
            datumAuthName = authName;
2476
0
            if (numericCode) {
2477
0
                datumCode =
2478
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2479
0
            } else {
2480
0
                datumCode = "GEODETIC_DATUM_" + code;
2481
0
            }
2482
0
            sqlStatements = self->getInsertStatementsFor(
2483
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2484
0
                allowedAuthorities);
2485
0
        }
2486
0
    } else {
2487
0
        const auto &datum = crs->datum();
2488
0
        assert(datum);
2489
0
        const auto datumNN = NN_NO_CHECK(datum);
2490
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2491
0
                               datumAuthName, datumCode);
2492
0
        if (datumAuthName.empty()) {
2493
0
            datumAuthName = authName;
2494
0
            if (numericCode) {
2495
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2496
0
            } else {
2497
0
                datumCode = "GEODETIC_DATUM_" + code;
2498
0
            }
2499
0
            sqlStatements =
2500
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2501
0
                                             numericCode, allowedAuthorities);
2502
0
        }
2503
0
    }
2504
2505
    // Find or insert coordinate system
2506
0
    const auto &coordinateSystem = crs->coordinateSystem();
2507
0
    std::string csAuthName;
2508
0
    std::string csCode;
2509
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2510
0
                     csAuthName, csCode, sqlStatements);
2511
2512
0
    const char *type = CRS_SUBTYPE_GEOG_2D;
2513
0
    if (coordinateSystem->axisList().size() == 3) {
2514
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2515
0
            type = CRS_SUBTYPE_GEOG_3D;
2516
0
        } else {
2517
0
            type = CRS_SUBTYPE_GEOCENTRIC;
2518
0
        }
2519
0
    }
2520
2521
    // Insert new record in geodetic_crs table
2522
0
    const auto sql =
2523
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2524
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2525
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2526
0
                        "", // description
2527
0
                        type, csAuthName.c_str(), csCode.c_str(),
2528
0
                        datumAuthName.c_str(), datumCode.c_str());
2529
0
    appendSql(sqlStatements, sql);
2530
2531
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2532
0
                           allowedAuthorities, sqlStatements);
2533
0
    return sqlStatements;
2534
0
}
2535
2536
// ---------------------------------------------------------------------------
2537
2538
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2539
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2540
    const std::string &code, bool numericCode,
2541
0
    const std::vector<std::string> &allowedAuthorities) {
2542
2543
0
    const auto self = NN_NO_CHECK(self_.lock());
2544
2545
0
    std::vector<std::string> sqlStatements;
2546
2547
    // Find or insert base geodetic CRS
2548
0
    const auto &baseCRS = crs->baseCRS();
2549
0
    std::string geodAuthName;
2550
0
    std::string geodCode;
2551
2552
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2553
0
    allowedAuthoritiesTmp.emplace_back(authName);
2554
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2555
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2556
0
        const auto candidates = baseCRS->identify(factory);
2557
0
        for (const auto &candidate : candidates) {
2558
0
            if (candidate.second == 100) {
2559
0
                const auto &ids = candidate.first->identifiers();
2560
0
                if (!ids.empty()) {
2561
0
                    const auto &id = ids.front();
2562
0
                    geodAuthName = *(id->codeSpace());
2563
0
                    geodCode = id->code();
2564
0
                    break;
2565
0
                }
2566
0
            }
2567
0
            if (!geodAuthName.empty()) {
2568
0
                break;
2569
0
            }
2570
0
        }
2571
0
    }
2572
0
    if (geodAuthName.empty()) {
2573
0
        geodAuthName = authName;
2574
0
        geodCode = "GEODETIC_CRS_" + code;
2575
0
        sqlStatements = self->getInsertStatementsFor(
2576
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2577
0
    }
2578
2579
    // Insert new record in conversion table
2580
0
    const auto &conversion = crs->derivingConversionRef();
2581
0
    std::string convAuthName(authName);
2582
0
    std::string convCode("CONVERSION_" + code);
2583
0
    if (numericCode) {
2584
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2585
0
    }
2586
0
    {
2587
0
        const auto &method = conversion->method();
2588
0
        const auto &methodIds = method->identifiers();
2589
0
        std::string methodAuthName;
2590
0
        std::string methodCode;
2591
0
        const operation::MethodMapping *methodMapping = nullptr;
2592
0
        if (methodIds.empty()) {
2593
0
            const int epsgCode = method->getEPSGCode();
2594
0
            if (epsgCode > 0) {
2595
0
                methodAuthName = metadata::Identifier::EPSG;
2596
0
                methodCode = toString(epsgCode);
2597
0
            } else {
2598
0
                const auto &methodName = method->nameStr();
2599
0
                size_t nProjectionMethodMappings = 0;
2600
0
                const auto projectionMethodMappings =
2601
0
                    operation::getProjectionMethodMappings(
2602
0
                        nProjectionMethodMappings);
2603
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2604
0
                    const auto &mapping = projectionMethodMappings[i];
2605
0
                    if (metadata::Identifier::isEquivalentName(
2606
0
                            mapping.wkt2_name, methodName.c_str())) {
2607
0
                        methodMapping = &mapping;
2608
0
                    }
2609
0
                }
2610
0
                if (methodMapping == nullptr ||
2611
0
                    methodMapping->proj_name_main == nullptr) {
2612
0
                    throw FactoryException("Cannot insert projection with "
2613
0
                                           "method without identifier");
2614
0
                }
2615
0
                methodAuthName = "PROJ";
2616
0
                methodCode = methodMapping->proj_name_main;
2617
0
                if (methodMapping->proj_name_aux) {
2618
0
                    methodCode += ' ';
2619
0
                    methodCode += methodMapping->proj_name_aux;
2620
0
                }
2621
0
            }
2622
0
        } else {
2623
0
            const auto &methodId = methodIds.front();
2624
0
            methodAuthName = *(methodId->codeSpace());
2625
0
            methodCode = methodId->code();
2626
0
        }
2627
2628
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2629
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2630
0
                                   convAuthName.c_str(), convCode.c_str(),
2631
0
                                   conversion->nameStr().c_str(),
2632
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2633
0
                                   method->nameStr().c_str());
2634
0
        const auto &srcValues = conversion->parameterValues();
2635
0
        if (srcValues.size() > N_MAX_PARAMS) {
2636
0
            throw FactoryException("Cannot insert projection with more than " +
2637
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2638
0
                                   " parameters");
2639
0
        }
2640
2641
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2642
0
        if (methodMapping == nullptr) {
2643
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2644
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2645
0
            } else {
2646
0
                methodMapping =
2647
0
                    operation::getMapping(method->nameStr().c_str());
2648
0
            }
2649
0
        }
2650
0
        if (methodMapping != nullptr) {
2651
            // Re-order projection parameters in their reference order
2652
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2653
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2654
0
                    auto opParamValue = dynamic_cast<
2655
0
                        const operation::OperationParameterValue *>(
2656
0
                        srcValues[i].get());
2657
0
                    if (!opParamValue) {
2658
0
                        throw FactoryException("Cannot insert projection with "
2659
0
                                               "non-OperationParameterValue");
2660
0
                    }
2661
0
                    if (methodMapping->params[j]->wkt2_name &&
2662
0
                        opParamValue->parameter()->nameStr() ==
2663
0
                            methodMapping->params[j]->wkt2_name) {
2664
0
                        values.emplace_back(srcValues[i]);
2665
0
                    }
2666
0
                }
2667
0
            }
2668
0
        }
2669
0
        if (values.size() != srcValues.size()) {
2670
0
            values = srcValues;
2671
0
        }
2672
2673
0
        for (const auto &genOpParamvalue : values) {
2674
0
            auto opParamValue =
2675
0
                dynamic_cast<const operation::OperationParameterValue *>(
2676
0
                    genOpParamvalue.get());
2677
0
            if (!opParamValue) {
2678
0
                throw FactoryException("Cannot insert projection with "
2679
0
                                       "non-OperationParameterValue");
2680
0
            }
2681
0
            const auto &param = opParamValue->parameter();
2682
0
            const auto &paramIds = param->identifiers();
2683
0
            std::string paramAuthName;
2684
0
            std::string paramCode;
2685
0
            if (paramIds.empty()) {
2686
0
                const int paramEPSGCode = param->getEPSGCode();
2687
0
                if (paramEPSGCode == 0) {
2688
0
                    throw FactoryException(
2689
0
                        "Cannot insert projection with method parameter "
2690
0
                        "without identifier");
2691
0
                }
2692
0
                paramAuthName = metadata::Identifier::EPSG;
2693
0
                paramCode = toString(paramEPSGCode);
2694
0
            } else {
2695
0
                const auto &paramId = paramIds.front();
2696
0
                paramAuthName = *(paramId->codeSpace());
2697
0
                paramCode = paramId->code();
2698
0
            }
2699
0
            const auto &value = opParamValue->parameterValue()->value();
2700
0
            const auto &unit = value.unit();
2701
0
            std::string uomAuthName;
2702
0
            std::string uomCode;
2703
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2704
0
                             sqlStatements);
2705
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2706
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2707
0
                                   param->nameStr().c_str(), value.value(),
2708
0
                                   uomAuthName.c_str(), uomCode.c_str());
2709
0
        }
2710
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2711
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2712
0
        }
2713
0
        sql += ",0);";
2714
0
        appendSql(sqlStatements, sql);
2715
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2716
0
                               allowedAuthorities, sqlStatements);
2717
0
    }
2718
2719
    // Find or insert coordinate system
2720
0
    const auto &coordinateSystem = crs->coordinateSystem();
2721
0
    std::string csAuthName;
2722
0
    std::string csCode;
2723
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2724
0
                     csAuthName, csCode, sqlStatements);
2725
2726
    // Insert new record in projected_crs table
2727
0
    const auto sql = formatStatement(
2728
0
        "INSERT INTO projected_crs VALUES("
2729
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2730
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2731
0
        "", // description
2732
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2733
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2734
0
    appendSql(sqlStatements, sql);
2735
2736
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2737
0
                           allowedAuthorities, sqlStatements);
2738
2739
0
    return sqlStatements;
2740
0
}
2741
2742
// ---------------------------------------------------------------------------
2743
2744
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2745
    const datum::VerticalReferenceFrameNNPtr &datum,
2746
    const std::string &authName, const std::string &code,
2747
    bool /* numericCode */,
2748
0
    const std::vector<std::string> &allowedAuthorities) {
2749
2750
0
    const auto self = NN_NO_CHECK(self_.lock());
2751
2752
0
    std::vector<std::string> sqlStatements;
2753
2754
    // Check if the object is already known under that code
2755
0
    std::string datumAuthName;
2756
0
    std::string datumCode;
2757
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2758
0
                           datumAuthName, datumCode);
2759
0
    if (datumAuthName == authName && datumCode == code) {
2760
0
        return {};
2761
0
    }
2762
2763
    // Insert new record in vertical_datum table
2764
0
    std::string publicationDate("NULL");
2765
0
    if (datum->publicationDate().has_value()) {
2766
0
        publicationDate = '\'';
2767
0
        publicationDate +=
2768
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2769
0
        publicationDate += '\'';
2770
0
    }
2771
0
    std::string frameReferenceEpoch("NULL");
2772
0
    const auto dynamicDatum =
2773
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2774
0
    if (dynamicDatum) {
2775
0
        frameReferenceEpoch =
2776
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2777
0
    }
2778
0
    const std::string anchor(*(datum->anchorDefinition()));
2779
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2780
0
    const auto sql = formatStatement(
2781
0
        "INSERT INTO vertical_datum VALUES("
2782
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2783
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2784
0
        "", // description
2785
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2786
0
        anchor.empty() ? nullptr : anchor.c_str(),
2787
0
        anchorEpoch.has_value()
2788
0
            ? anchorEpochToStr(
2789
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2790
0
                  .c_str()
2791
0
            : "NULL");
2792
0
    appendSql(sqlStatements, sql);
2793
2794
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2795
0
                           allowedAuthorities, sqlStatements);
2796
2797
0
    return sqlStatements;
2798
0
}
2799
2800
// ---------------------------------------------------------------------------
2801
2802
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2803
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2804
    const std::string &code, bool numericCode,
2805
0
    const std::vector<std::string> &allowedAuthorities) {
2806
2807
0
    const auto self = NN_NO_CHECK(self_.lock());
2808
2809
0
    std::vector<std::string> sqlStatements;
2810
2811
    // Find or insert datum/datum ensemble
2812
0
    std::string datumAuthName;
2813
0
    std::string datumCode;
2814
0
    const auto &ensemble = crs->datumEnsemble();
2815
0
    if (ensemble) {
2816
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2817
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2818
0
                               datumAuthName, datumCode);
2819
0
        if (datumAuthName.empty()) {
2820
0
            datumAuthName = authName;
2821
0
            if (numericCode) {
2822
0
                datumCode =
2823
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2824
0
            } else {
2825
0
                datumCode = "VERTICAL_DATUM_" + code;
2826
0
            }
2827
0
            sqlStatements = self->getInsertStatementsFor(
2828
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2829
0
                allowedAuthorities);
2830
0
        }
2831
0
    } else {
2832
0
        const auto &datum = crs->datum();
2833
0
        assert(datum);
2834
0
        const auto datumNN = NN_NO_CHECK(datum);
2835
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2836
0
                               datumAuthName, datumCode);
2837
0
        if (datumAuthName.empty()) {
2838
0
            datumAuthName = authName;
2839
0
            if (numericCode) {
2840
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2841
0
            } else {
2842
0
                datumCode = "VERTICAL_DATUM_" + code;
2843
0
            }
2844
0
            sqlStatements =
2845
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2846
0
                                             numericCode, allowedAuthorities);
2847
0
        }
2848
0
    }
2849
2850
    // Find or insert coordinate system
2851
0
    const auto &coordinateSystem = crs->coordinateSystem();
2852
0
    std::string csAuthName;
2853
0
    std::string csCode;
2854
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2855
0
                     csAuthName, csCode, sqlStatements);
2856
2857
    // Insert new record in vertical_crs table
2858
0
    const auto sql =
2859
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2860
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2861
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2862
0
                        "", // description
2863
0
                        csAuthName.c_str(), csCode.c_str(),
2864
0
                        datumAuthName.c_str(), datumCode.c_str());
2865
0
    appendSql(sqlStatements, sql);
2866
2867
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2868
0
                           allowedAuthorities, sqlStatements);
2869
2870
0
    return sqlStatements;
2871
0
}
2872
2873
// ---------------------------------------------------------------------------
2874
2875
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2876
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2877
    const std::string &code, bool numericCode,
2878
0
    const std::vector<std::string> &allowedAuthorities) {
2879
2880
0
    const auto self = NN_NO_CHECK(self_.lock());
2881
2882
0
    std::vector<std::string> sqlStatements;
2883
2884
0
    int counter = 1;
2885
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2886
0
    const auto &components = crs->componentReferenceSystems();
2887
0
    if (components.size() != 2) {
2888
0
        throw FactoryException(
2889
0
            "Cannot insert compound CRS with number of components != 2");
2890
0
    }
2891
2892
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2893
0
    allowedAuthoritiesTmp.emplace_back(authName);
2894
2895
0
    for (const auto &component : components) {
2896
0
        std::string compAuthName;
2897
0
        std::string compCode;
2898
2899
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2900
0
            const auto factory =
2901
0
                AuthorityFactory::create(self, allowedAuthority);
2902
0
            const auto candidates = component->identify(factory);
2903
0
            for (const auto &candidate : candidates) {
2904
0
                if (candidate.second == 100) {
2905
0
                    const auto &ids = candidate.first->identifiers();
2906
0
                    if (!ids.empty()) {
2907
0
                        const auto &id = ids.front();
2908
0
                        compAuthName = *(id->codeSpace());
2909
0
                        compCode = id->code();
2910
0
                        break;
2911
0
                    }
2912
0
                }
2913
0
                if (!compAuthName.empty()) {
2914
0
                    break;
2915
0
                }
2916
0
            }
2917
0
        }
2918
2919
0
        if (compAuthName.empty()) {
2920
0
            compAuthName = authName;
2921
0
            if (numericCode) {
2922
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2923
0
            } else {
2924
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2925
0
            }
2926
0
            const auto sqlStatementsTmp =
2927
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2928
0
                                             numericCode, allowedAuthorities);
2929
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2930
0
                                 sqlStatementsTmp.end());
2931
0
        }
2932
2933
0
        componentsId.emplace_back(
2934
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2935
2936
0
        ++counter;
2937
0
    }
2938
2939
    // Insert new record in compound_crs table
2940
0
    const auto sql = formatStatement(
2941
0
        "INSERT INTO compound_crs VALUES("
2942
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2943
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2944
0
        "", // description
2945
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2946
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2947
0
    appendSql(sqlStatements, sql);
2948
2949
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2950
0
                           allowedAuthorities, sqlStatements);
2951
2952
0
    return sqlStatements;
2953
0
}
2954
2955
//! @endcond
2956
2957
// ---------------------------------------------------------------------------
2958
2959
//! @cond Doxygen_Suppress
2960
9.89k
DatabaseContext::~DatabaseContext() {
2961
9.89k
    try {
2962
9.89k
        stopInsertStatementsSession();
2963
9.89k
    } catch (const std::exception &) {
2964
0
    }
2965
9.89k
}
2966
//! @endcond
2967
2968
// ---------------------------------------------------------------------------
2969
2970
9.89k
DatabaseContext::DatabaseContext() : d(std::make_unique<Private>()) {}
2971
2972
// ---------------------------------------------------------------------------
2973
2974
/** \brief Instantiate a database context.
2975
 *
2976
 * This database context should be used only by one thread at a time.
2977
 *
2978
 * @param databasePath Path and filename of the database. Might be empty
2979
 * string for the default rules to locate the default proj.db
2980
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2981
 * Might be empty.
2982
 * Starting with PROJ 8.1, if this parameter is an empty array,
2983
 * the PROJ_AUX_DB environment variable will be used, if set.
2984
 * It must contain one or several paths. If several paths are
2985
 * provided, they must be separated by the colon (:) character on Unix, and
2986
 * on Windows, by the semi-colon (;) character.
2987
 * @param ctx Context used for file search.
2988
 * @throw FactoryException if the database cannot be opened.
2989
 */
2990
DatabaseContextNNPtr
2991
DatabaseContext::create(const std::string &databasePath,
2992
                        const std::vector<std::string> &auxiliaryDatabasePaths,
2993
9.89k
                        PJ_CONTEXT *ctx) {
2994
9.89k
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
2995
9.89k
    auto dbCtxPrivate = dbCtx->getPrivate();
2996
9.89k
    dbCtxPrivate->open(databasePath, ctx);
2997
9.89k
    auto auxDbs(auxiliaryDatabasePaths);
2998
9.89k
    if (auxDbs.empty()) {
2999
9.89k
        const char *auxDbStr = getenv("PROJ_AUX_DB");
3000
9.89k
        if (auxDbStr) {
3001
#ifdef _WIN32
3002
            const char *delim = ";";
3003
#else
3004
0
            const char *delim = ":";
3005
0
#endif
3006
0
            auxDbs = split(auxDbStr, delim);
3007
0
        }
3008
9.89k
    }
3009
9.89k
    if (!auxDbs.empty()) {
3010
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
3011
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
3012
0
    }
3013
9.89k
    dbCtxPrivate->self_ = dbCtx.as_nullable();
3014
9.89k
    return dbCtx;
3015
9.89k
}
3016
3017
// ---------------------------------------------------------------------------
3018
3019
/** \brief Return the list of authorities used in the database.
3020
 */
3021
102
std::set<std::string> DatabaseContext::getAuthorities() const {
3022
102
    auto res = d->run("SELECT auth_name FROM authority_list");
3023
102
    std::set<std::string> list;
3024
816
    for (const auto &row : res) {
3025
816
        list.insert(row[0]);
3026
816
    }
3027
102
    return list;
3028
102
}
3029
3030
// ---------------------------------------------------------------------------
3031
3032
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
3033
 * CREATE VIEW) needed to initialize a new database.
3034
 */
3035
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
3036
0
    return d->getDatabaseStructure();
3037
0
}
3038
3039
// ---------------------------------------------------------------------------
3040
3041
/** \brief Return the path to the database.
3042
 */
3043
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
3044
3045
// ---------------------------------------------------------------------------
3046
3047
/** \brief Return a metadata item.
3048
 *
3049
 * Value remains valid while this is alive and to the next call to getMetadata
3050
 */
3051
0
const char *DatabaseContext::getMetadata(const char *key) const {
3052
0
    auto res =
3053
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
3054
0
    if (res.empty()) {
3055
0
        return nullptr;
3056
0
    }
3057
0
    d->lastMetadataValue_ = res.front()[0];
3058
0
    return d->lastMetadataValue_.c_str();
3059
0
}
3060
3061
// ---------------------------------------------------------------------------
3062
3063
/** \brief Starts a session for getInsertStatementsFor()
3064
 *
3065
 * Starts a new session for one or several calls to getInsertStatementsFor().
3066
 * An insertion session guarantees that the inserted objects will not create
3067
 * conflicting intermediate objects.
3068
 *
3069
 * The session must be stopped with stopInsertStatementsSession().
3070
 *
3071
 * Only one session may be active at a time for a given database context.
3072
 *
3073
 * @throw FactoryException in case of error.
3074
 * @since 8.1
3075
 */
3076
0
void DatabaseContext::startInsertStatementsSession() {
3077
0
    if (d->memoryDbHandle_) {
3078
0
        throw FactoryException(
3079
0
            "startInsertStatementsSession() cannot be invoked until "
3080
0
            "stopInsertStatementsSession() is.");
3081
0
    }
3082
3083
0
    d->memoryDbForInsertPath_.clear();
3084
0
    const auto sqlStatements = getDatabaseStructure();
3085
3086
    // Create a in-memory temporary sqlite3 database
3087
0
    std::ostringstream buffer;
3088
0
    buffer << "file:temp_db_for_insert_statements_";
3089
0
    buffer << this;
3090
0
    buffer << ".db?mode=memory&cache=shared";
3091
0
    d->memoryDbForInsertPath_ = buffer.str();
3092
0
    sqlite3 *memoryDbHandle = nullptr;
3093
0
    sqlite3_open_v2(
3094
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3095
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3096
0
    if (memoryDbHandle == nullptr) {
3097
0
        throw FactoryException("Cannot create in-memory database");
3098
0
    }
3099
0
    d->memoryDbHandle_ =
3100
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3101
3102
    // Fill the structure of this database
3103
0
    for (const auto &sql : sqlStatements) {
3104
0
        char *errmsg = nullptr;
3105
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3106
0
                         nullptr, &errmsg) != SQLITE_OK) {
3107
0
            const auto sErrMsg =
3108
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3109
0
            sqlite3_free(errmsg);
3110
0
            throw FactoryException(sErrMsg);
3111
0
        }
3112
0
        sqlite3_free(errmsg);
3113
0
    }
3114
3115
    // Attach this database to the current one(s)
3116
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3117
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3118
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3119
0
}
3120
3121
// ---------------------------------------------------------------------------
3122
3123
/** \brief Suggests a database code for the passed object.
3124
 *
3125
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3126
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3127
 *
3128
 * @param object Object for which to suggest a code.
3129
 * @param authName Authority name into which the object will be inserted.
3130
 * @param numericCode Whether the code should be numeric, or derived from the
3131
 * object name.
3132
 * @return the suggested code, that is guaranteed to not conflict with an
3133
 * existing one.
3134
 *
3135
 * @throw FactoryException in case of error.
3136
 * @since 8.1
3137
 */
3138
std::string
3139
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3140
                                 const std::string &authName,
3141
0
                                 bool numericCode) {
3142
0
    const char *tableName = "prime_meridian";
3143
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3144
        // tableName = "prime_meridian";
3145
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3146
0
        tableName = "ellipsoid";
3147
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3148
0
                   object.get())) {
3149
0
        tableName = "geodetic_datum";
3150
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3151
0
                   object.get())) {
3152
0
        tableName = "vertical_datum";
3153
0
    } else if (const auto ensemble =
3154
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3155
0
        const auto &datums = ensemble->datums();
3156
0
        if (!datums.empty() &&
3157
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3158
0
                datums[0].get())) {
3159
0
            tableName = "geodetic_datum";
3160
0
        } else {
3161
0
            tableName = "vertical_datum";
3162
0
        }
3163
0
    } else if (const auto boundCRS =
3164
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3165
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3166
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3167
0
        tableName = "crs_view";
3168
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3169
0
        tableName = "conversion";
3170
0
    } else {
3171
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3172
0
    }
3173
3174
0
    if (numericCode) {
3175
0
        std::string sql("SELECT MAX(code) FROM ");
3176
0
        sql += tableName;
3177
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3178
0
               "AND upper(code) = lower(code)";
3179
0
        const auto res = d->run(sql, {authName});
3180
0
        if (res.empty()) {
3181
0
            return "1";
3182
0
        }
3183
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3184
0
    }
3185
3186
0
    std::string code;
3187
0
    code.reserve(object->nameStr().size());
3188
0
    bool insertUnderscore = false;
3189
0
    for (const auto ch : toupper(object->nameStr())) {
3190
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3191
0
            if (insertUnderscore && code.back() != '_')
3192
0
                code += '_';
3193
0
            code += ch;
3194
0
            insertUnderscore = false;
3195
0
        } else {
3196
0
            insertUnderscore = true;
3197
0
        }
3198
0
    }
3199
0
    return d->findFreeCode(tableName, authName, code);
3200
0
}
3201
3202
// ---------------------------------------------------------------------------
3203
3204
/** \brief Returns SQL statements needed to insert the passed object into the
3205
 * database.
3206
 *
3207
 * startInsertStatementsSession() must have been called previously.
3208
 *
3209
 * @param object The object to insert into the database. Currently only
3210
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3211
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3212
 * @param authName Authority name into which the object will be inserted.
3213
 * @param code Code with which the object will be inserted.
3214
 * @param numericCode Whether intermediate objects that can be created should
3215
 *                    use numeric codes (true), or may be alphanumeric (false)
3216
 * @param allowedAuthorities Authorities to which intermediate objects are
3217
 *                           allowed to refer to. authName will be implicitly
3218
 *                           added to it. Note that unit, coordinate
3219
 *                           systems, projection methods and parameters will in
3220
 *                           any case be allowed to refer to EPSG.
3221
 * @throw FactoryException in case of error.
3222
 * @since 8.1
3223
 */
3224
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3225
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3226
    const std::string &code, bool numericCode,
3227
0
    const std::vector<std::string> &allowedAuthorities) {
3228
0
    if (d->memoryDbHandle_ == nullptr) {
3229
0
        throw FactoryException(
3230
0
            "startInsertStatementsSession() should be invoked first");
3231
0
    }
3232
3233
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3234
0
    if (crs) {
3235
        // Check if the object is already known under that code
3236
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3237
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3238
0
        allowedAuthoritiesTmp.emplace_back(authName);
3239
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3240
0
            const auto factory =
3241
0
                AuthorityFactory::create(self, allowedAuthority);
3242
0
            const auto candidates = crs->identify(factory);
3243
0
            for (const auto &candidate : candidates) {
3244
0
                if (candidate.second == 100) {
3245
0
                    const auto &ids = candidate.first->identifiers();
3246
0
                    for (const auto &id : ids) {
3247
0
                        if (*(id->codeSpace()) == authName &&
3248
0
                            id->code() == code) {
3249
0
                            return {};
3250
0
                        }
3251
0
                    }
3252
0
                }
3253
0
            }
3254
0
        }
3255
0
    }
3256
3257
0
    if (const auto pm =
3258
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3259
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3260
0
                                         numericCode, allowedAuthorities);
3261
0
    }
3262
3263
0
    else if (const auto ellipsoid =
3264
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3265
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3266
0
                                         numericCode, allowedAuthorities);
3267
0
    }
3268
3269
0
    else if (const auto geodeticDatum =
3270
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3271
0
                     object)) {
3272
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3273
0
                                         code, numericCode, allowedAuthorities);
3274
0
    }
3275
3276
0
    else if (const auto ensemble =
3277
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3278
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3279
0
                                         numericCode, allowedAuthorities);
3280
0
    }
3281
3282
0
    else if (const auto geodCRS =
3283
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3284
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3285
0
                                         numericCode, allowedAuthorities);
3286
0
    }
3287
3288
0
    else if (const auto projCRS =
3289
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3290
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3291
0
                                         numericCode, allowedAuthorities);
3292
0
    }
3293
3294
0
    else if (const auto verticalDatum =
3295
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3296
0
                     object)) {
3297
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3298
0
                                         code, numericCode, allowedAuthorities);
3299
0
    }
3300
3301
0
    else if (const auto vertCRS =
3302
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3303
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3304
0
                                         numericCode, allowedAuthorities);
3305
0
    }
3306
3307
0
    else if (const auto compoundCRS =
3308
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3309
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3310
0
                                         code, numericCode, allowedAuthorities);
3311
0
    }
3312
3313
0
    else if (const auto boundCRS =
3314
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3315
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3316
0
                                      numericCode, allowedAuthorities);
3317
0
    }
3318
3319
0
    else {
3320
0
        throw FactoryException(
3321
0
            "getInsertStatementsFor(): unhandled type of object");
3322
0
    }
3323
0
}
3324
3325
// ---------------------------------------------------------------------------
3326
3327
/** \brief Stops an insertion session started with
3328
 * startInsertStatementsSession()
3329
 *
3330
 * @since 8.1
3331
 */
3332
9.89k
void DatabaseContext::stopInsertStatementsSession() {
3333
9.89k
    if (d->memoryDbHandle_) {
3334
0
        d->clearCaches();
3335
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3336
0
        d->memoryDbHandle_.reset();
3337
0
        d->memoryDbForInsertPath_.clear();
3338
0
    }
3339
9.89k
}
3340
3341
// ---------------------------------------------------------------------------
3342
3343
//! @cond Doxygen_Suppress
3344
3345
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3346
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3347
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3348
0
    return ctxt;
3349
0
}
3350
3351
// ---------------------------------------------------------------------------
3352
3353
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3354
3355
// ---------------------------------------------------------------------------
3356
3357
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3358
                                             std::string &projFilename,
3359
                                             std::string &projFormat,
3360
114k
                                             bool &inverse) const {
3361
114k
    auto res = d->run(
3362
114k
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3363
114k
        "grid_alternatives WHERE original_grid_name = ? AND "
3364
114k
        "proj_grid_name <> ''",
3365
114k
        {officialName});
3366
114k
    if (res.empty()) {
3367
3.14k
        return false;
3368
3.14k
    }
3369
111k
    const auto &row = res.front();
3370
111k
    projFilename = row[0];
3371
111k
    projFormat = row[1];
3372
111k
    inverse = row[2] == "1";
3373
111k
    return true;
3374
114k
}
3375
3376
// ---------------------------------------------------------------------------
3377
3378
static std::string makeCachedGridKey(const std::string &projFilename,
3379
                                     bool networkEnabled,
3380
143k
                                     bool considerKnownGridsAsAvailable) {
3381
143k
    std::string key(projFilename);
3382
143k
    key += networkEnabled ? "true" : "false";
3383
143k
    key += considerKnownGridsAsAvailable ? "true" : "false";
3384
143k
    return key;
3385
143k
}
3386
3387
// ---------------------------------------------------------------------------
3388
3389
/** Invalidates information related to projFilename that might have been
3390
 * previously cached by lookForGridInfo().
3391
 *
3392
 * This is useful when downloading a new grid during a session.
3393
 */
3394
0
void DatabaseContext::invalidateGridInfo(const std::string &projFilename) {
3395
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false));
3396
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true));
3397
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false));
3398
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true));
3399
0
}
3400
3401
// ---------------------------------------------------------------------------
3402
3403
bool DatabaseContext::lookForGridInfo(
3404
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3405
    std::string &fullFilename, std::string &packageName, std::string &url,
3406
143k
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3407
143k
    Private::GridInfoCache info;
3408
3409
143k
    if (projFilename == "null") {
3410
        // Special case for implicit "null" grid.
3411
400
        fullFilename.clear();
3412
400
        packageName.clear();
3413
400
        url.clear();
3414
400
        directDownload = false;
3415
400
        openLicense = true;
3416
400
        gridAvailable = true;
3417
400
        return true;
3418
400
    }
3419
3420
143k
    auto ctxt = d->pjCtxt();
3421
143k
    if (ctxt == nullptr) {
3422
0
        ctxt = pj_get_default_ctx();
3423
0
        d->setPjCtxt(ctxt);
3424
0
    }
3425
3426
143k
    const std::string key(makeCachedGridKey(
3427
143k
        projFilename, proj_context_is_network_enabled(ctxt) != false,
3428
143k
        considerKnownGridsAsAvailable));
3429
143k
    if (d->getGridInfoFromCache(key, info)) {
3430
102k
        fullFilename = info.fullFilename;
3431
102k
        packageName = info.packageName;
3432
102k
        url = info.url;
3433
102k
        directDownload = info.directDownload;
3434
102k
        openLicense = info.openLicense;
3435
102k
        gridAvailable = info.gridAvailable;
3436
102k
        return info.found;
3437
102k
    }
3438
3439
40.9k
    fullFilename.clear();
3440
40.9k
    packageName.clear();
3441
40.9k
    url.clear();
3442
40.9k
    openLicense = false;
3443
40.9k
    directDownload = false;
3444
40.9k
    gridAvailable = false;
3445
3446
40.9k
    const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() {
3447
40.9k
        fullFilename.resize(2048);
3448
40.9k
        const int errno_before = proj_context_errno(ctxt);
3449
40.9k
        bool lGridAvailable = NS_PROJ::FileManager::open_resource_file(
3450
40.9k
                                  ctxt, projFilename.c_str(), &fullFilename[0],
3451
40.9k
                                  fullFilename.size() - 1) != nullptr;
3452
40.9k
        proj_context_errno_set(ctxt, errno_before);
3453
40.9k
        fullFilename.resize(strlen(fullFilename.c_str()));
3454
40.9k
        return lGridAvailable;
3455
40.9k
    };
3456
3457
40.9k
    auto res =
3458
40.9k
        d->run("SELECT "
3459
40.9k
               "grid_packages.package_name, "
3460
40.9k
               "grid_alternatives.url, "
3461
40.9k
               "grid_packages.url AS package_url, "
3462
40.9k
               "grid_alternatives.open_license, "
3463
40.9k
               "grid_packages.open_license AS package_open_license, "
3464
40.9k
               "grid_alternatives.direct_download, "
3465
40.9k
               "grid_packages.direct_download AS package_direct_download, "
3466
40.9k
               "grid_alternatives.proj_grid_name, "
3467
40.9k
               "grid_alternatives.old_proj_grid_name "
3468
40.9k
               "FROM grid_alternatives "
3469
40.9k
               "LEFT JOIN grid_packages ON "
3470
40.9k
               "grid_alternatives.package_name = grid_packages.package_name "
3471
40.9k
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3472
40.9k
               {projFilename, projFilename});
3473
40.9k
    bool ret = !res.empty();
3474
40.9k
    if (ret) {
3475
29.0k
        const auto &row = res.front();
3476
29.0k
        packageName = std::move(row[0]);
3477
29.0k
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3478
29.0k
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3479
29.0k
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3480
3481
29.0k
        const auto &proj_grid_name = row[7];
3482
29.0k
        const auto &old_proj_grid_name = row[8];
3483
29.0k
        if (proj_grid_name != old_proj_grid_name &&
3484
29.0k
            old_proj_grid_name == projFilename) {
3485
114
            std::string fullFilenameNewName;
3486
114
            fullFilenameNewName.resize(2048);
3487
114
            const int errno_before = proj_context_errno(ctxt);
3488
114
            bool gridAvailableWithNewName =
3489
114
                pj_find_file(ctxt, proj_grid_name.c_str(),
3490
114
                             &fullFilenameNewName[0],
3491
114
                             fullFilenameNewName.size() - 1) != 0;
3492
114
            proj_context_errno_set(ctxt, errno_before);
3493
114
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3494
114
            if (gridAvailableWithNewName) {
3495
0
                gridAvailable = true;
3496
0
                fullFilename = std::move(fullFilenameNewName);
3497
0
            }
3498
114
        }
3499
3500
29.0k
        if (!gridAvailable && considerKnownGridsAsAvailable &&
3501
74
            (!packageName.empty() || (!url.empty() && openLicense))) {
3502
3503
            // In considerKnownGridsAsAvailable mode, try to fetch the local
3504
            // file name if it exists, but do not attempt network access.
3505
2
            const auto network_was_enabled =
3506
2
                proj_context_is_network_enabled(ctxt);
3507
2
            proj_context_set_enable_network(ctxt, false);
3508
2
            (void)resolveFullFilename();
3509
2
            proj_context_set_enable_network(ctxt, network_was_enabled);
3510
3511
2
            gridAvailable = true;
3512
2
        }
3513
3514
29.0k
        info.packageName = packageName;
3515
29.0k
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3516
29.0k
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3517
28.8k
            if (endpoint.back() != '/') {
3518
28.8k
                endpoint += '/';
3519
28.8k
            }
3520
28.8k
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3521
28.8k
        }
3522
29.0k
        info.directDownload = directDownload;
3523
29.0k
        info.openLicense = openLicense;
3524
3525
29.0k
        if (!gridAvailable) {
3526
29.0k
            gridAvailable = resolveFullFilename();
3527
29.0k
        }
3528
29.0k
    } else {
3529
11.8k
        gridAvailable = resolveFullFilename();
3530
3531
11.8k
        if (starts_with(fullFilename, "http://") ||
3532
11.8k
            starts_with(fullFilename, "https://")) {
3533
0
            url = fullFilename;
3534
0
            fullFilename.clear();
3535
0
        }
3536
11.8k
    }
3537
3538
40.9k
    info.fullFilename = fullFilename;
3539
40.9k
    info.url = url;
3540
40.9k
    info.gridAvailable = gridAvailable;
3541
40.9k
    info.found = ret;
3542
40.9k
    d->cache(key, info);
3543
40.9k
    return ret;
3544
143k
}
3545
3546
// ---------------------------------------------------------------------------
3547
3548
/** Returns the number of queries to the database since the creation of this
3549
 * instance.
3550
 */
3551
0
unsigned int DatabaseContext::getQueryCounter() const {
3552
0
    return d->queryCounter_;
3553
0
}
3554
3555
// ---------------------------------------------------------------------------
3556
3557
bool DatabaseContext::isKnownName(const std::string &name,
3558
0
                                  const std::string &tableName) const {
3559
0
    std::string sql("SELECT 1 FROM \"");
3560
0
    sql += replaceAll(tableName, "\"", "\"\"");
3561
0
    sql += "\" WHERE name = ? LIMIT 1";
3562
0
    return !d->run(sql, {name}).empty();
3563
0
}
3564
// ---------------------------------------------------------------------------
3565
3566
std::string
3567
31.3k
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3568
31.3k
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3569
31.3k
                      "old_proj_grid_name = ?",
3570
31.3k
                      {oldProjGridName});
3571
31.3k
    if (res.empty()) {
3572
30.8k
        return std::string();
3573
30.8k
    }
3574
496
    return res.front()[0];
3575
31.3k
}
3576
3577
// ---------------------------------------------------------------------------
3578
3579
29.5k
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3580
29.5k
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3581
29.5k
                      "proj_grid_name = ?",
3582
29.5k
                      {gridName});
3583
29.5k
    if (res.empty()) {
3584
503
        return std::string();
3585
503
    }
3586
29.0k
    return res.front()[0];
3587
29.5k
}
3588
3589
// ---------------------------------------------------------------------------
3590
3591
// scripts/build_db_from_esri.py adds a second alias for
3592
// names that have '[' in them. See get_old_esri_name()
3593
// in scripts/build_db_from_esri.py
3594
// So if we only have two aliases detect that situation to get the official
3595
// new name
3596
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3597
0
    std::string first = l.front();
3598
0
    std::string second = *(std::next(l.begin()));
3599
0
    if (second.find('[') != std::string::npos)
3600
0
        std::swap(first, second);
3601
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3602
0
        second) {
3603
0
        return first;
3604
0
    }
3605
0
    return std::string();
3606
0
}
3607
3608
// ---------------------------------------------------------------------------
3609
3610
/** \brief Gets the alias name from an official name.
3611
 *
3612
 * @param officialName Official name. Mandatory
3613
 * @param tableName Table name/category. Mandatory.
3614
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3615
 *                  accepted as special names to add a constraint on the "type"
3616
 *                  column of the "geodetic_crs" table.
3617
 * @param source Source of the alias. Mandatory
3618
 * @return Alias name (or empty if not found).
3619
 * @throw FactoryException in case of error.
3620
 */
3621
std::string
3622
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3623
                                          const std::string &tableName,
3624
0
                                          const std::string &source) const {
3625
0
    std::string sql("SELECT auth_name, code FROM \"");
3626
0
    const auto genuineTableName =
3627
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3628
0
            ? "geodetic_crs"
3629
0
            : tableName;
3630
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3631
0
    sql += "\" WHERE name = ?";
3632
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3633
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3634
0
    } else if (tableName == "geographic_3D_crs") {
3635
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3636
0
    }
3637
0
    sql += " ORDER BY deprecated";
3638
0
    auto res = d->run(sql, {officialName});
3639
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3640
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3641
    // NAD83(HARN), and that's definitely not desirable.
3642
0
    if (res.empty() &&
3643
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3644
0
        res = d->run(
3645
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3646
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3647
0
            {genuineTableName, officialName});
3648
0
        if (res.size() != 1) {
3649
0
            return std::string();
3650
0
        }
3651
0
    }
3652
0
    for (const auto &row : res) {
3653
0
        auto res2 =
3654
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3655
0
                   "auth_name = ? AND code = ? AND source = ?",
3656
0
                   {genuineTableName, row[0], row[1], source});
3657
0
        if (!res2.empty()) {
3658
0
            if (res2.size() == 2 && source == "ESRI") {
3659
0
                std::list<std::string> l;
3660
0
                l.emplace_back(res2.front()[0]);
3661
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3662
0
                std::string uniqueEsriAlias = getUniqueEsriAlias(l);
3663
0
                if (!uniqueEsriAlias.empty())
3664
0
                    return uniqueEsriAlias;
3665
0
            }
3666
0
            return res2.front()[0];
3667
0
        }
3668
0
    }
3669
0
    return std::string();
3670
0
}
3671
3672
// ---------------------------------------------------------------------------
3673
3674
/** \brief Gets the alias names for an object.
3675
 *
3676
 * Either authName + code or officialName must be non empty.
3677
 *
3678
 * @param authName Authority.
3679
 * @param code Code.
3680
 * @param officialName Official name.
3681
 * @param tableName Table name/category. Mandatory.
3682
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3683
 *                  accepted as special names to add a constraint on the "type"
3684
 *                  column of the "geodetic_crs" table.
3685
 * @param source Source of the alias. May be empty.
3686
 * @return Aliases
3687
 */
3688
std::list<std::string> DatabaseContext::getAliases(
3689
    const std::string &authName, const std::string &code,
3690
    const std::string &officialName, const std::string &tableName,
3691
266k
    const std::string &source) const {
3692
3693
266k
    std::list<std::string> res;
3694
266k
    const auto key(authName + code + officialName + tableName + source);
3695
266k
    if (d->cacheAliasNames_.tryGet(key, res)) {
3696
257k
        return res;
3697
257k
    }
3698
3699
9.41k
    std::string resolvedAuthName(authName);
3700
9.41k
    std::string resolvedCode(code);
3701
9.41k
    const auto genuineTableName =
3702
9.41k
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3703
9.41k
            ? "geodetic_crs"
3704
9.41k
            : tableName;
3705
9.41k
    if (authName.empty() || code.empty()) {
3706
5.46k
        std::string sql("SELECT auth_name, code FROM \"");
3707
5.46k
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3708
5.46k
        sql += "\" WHERE name = ?";
3709
5.46k
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3710
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3711
5.46k
        } else if (tableName == "geographic_3D_crs") {
3712
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3713
0
        }
3714
5.46k
        sql += " ORDER BY deprecated";
3715
5.46k
        auto resSql = d->run(sql, {officialName});
3716
5.46k
        if (resSql.empty()) {
3717
3.31k
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3718
3.31k
                            "table_name = ? AND "
3719
3.31k
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3720
3.31k
                            {genuineTableName, officialName});
3721
3.31k
            if (resSql.size() != 1) {
3722
1.71k
                d->cacheAliasNames_.insert(key, res);
3723
1.71k
                return res;
3724
1.71k
            }
3725
3.31k
        }
3726
3.75k
        const auto &row = resSql.front();
3727
3.75k
        resolvedAuthName = row[0];
3728
3.75k
        resolvedCode = row[1];
3729
3.75k
    }
3730
7.69k
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3731
7.69k
                    "auth_name = ? AND code = ?");
3732
7.69k
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3733
7.69k
    if (source == "not EPSG_OLD") {
3734
45
        sql += " AND source != 'EPSG_OLD'";
3735
7.65k
    } else if (!source.empty()) {
3736
0
        sql += " AND source = ?";
3737
0
        params.emplace_back(source);
3738
0
    }
3739
7.69k
    auto resSql = d->run(sql, params);
3740
27.8k
    for (const auto &row : resSql) {
3741
27.8k
        res.emplace_back(row[0]);
3742
27.8k
    }
3743
3744
7.69k
    if (res.size() == 2 && source == "ESRI") {
3745
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3746
0
        if (!uniqueEsriAlias.empty()) {
3747
0
            res.clear();
3748
0
            res.emplace_back(uniqueEsriAlias);
3749
0
        }
3750
0
    }
3751
3752
7.69k
    d->cacheAliasNames_.insert(key, res);
3753
7.69k
    return res;
3754
9.41k
}
3755
3756
// ---------------------------------------------------------------------------
3757
3758
/** \brief Return the 'name' column of a table for an object
3759
 *
3760
 * @param tableName Table name/category.
3761
 * @param authName Authority name of the object.
3762
 * @param code Code of the object
3763
 * @return Name (or empty)
3764
 * @throw FactoryException in case of error.
3765
 */
3766
std::string DatabaseContext::getName(const std::string &tableName,
3767
                                     const std::string &authName,
3768
126k
                                     const std::string &code) const {
3769
126k
    std::string res;
3770
126k
    const auto key(tableName + authName + code);
3771
126k
    if (d->cacheNames_.tryGet(key, res)) {
3772
122k
        return res;
3773
122k
    }
3774
3775
3.67k
    std::string sql("SELECT name FROM \"");
3776
3.67k
    sql += replaceAll(tableName, "\"", "\"\"");
3777
3.67k
    sql += "\" WHERE auth_name = ? AND code = ?";
3778
3.67k
    auto sqlRes = d->run(sql, {authName, code});
3779
3.67k
    if (sqlRes.empty()) {
3780
2
        res.clear();
3781
3.66k
    } else {
3782
3.66k
        res = sqlRes.front()[0];
3783
3.66k
    }
3784
3.67k
    d->cacheNames_.insert(key, res);
3785
3.67k
    return res;
3786
126k
}
3787
3788
// ---------------------------------------------------------------------------
3789
3790
/** \brief Return the 'text_definition' column of a table for an object
3791
 *
3792
 * @param tableName Table name/category.
3793
 * @param authName Authority name of the object.
3794
 * @param code Code of the object
3795
 * @return Text definition (or empty)
3796
 * @throw FactoryException in case of error.
3797
 */
3798
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3799
                                               const std::string &authName,
3800
0
                                               const std::string &code) const {
3801
0
    std::string sql("SELECT text_definition FROM \"");
3802
0
    sql += replaceAll(tableName, "\"", "\"\"");
3803
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3804
0
    auto res = d->run(sql, {authName, code});
3805
0
    if (res.empty()) {
3806
0
        return std::string();
3807
0
    }
3808
0
    return res.front()[0];
3809
0
}
3810
3811
// ---------------------------------------------------------------------------
3812
3813
/** \brief Return the allowed authorities when researching transformations
3814
 * between different authorities.
3815
 *
3816
 * @throw FactoryException in case of error.
3817
 */
3818
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3819
    const std::string &sourceAuthName,
3820
79.5k
    const std::string &targetAuthName) const {
3821
3822
79.5k
    const auto key(sourceAuthName + targetAuthName);
3823
79.5k
    auto hit = d->cacheAllowedAuthorities_.find(key);
3824
79.5k
    if (hit != d->cacheAllowedAuthorities_.end()) {
3825
77.4k
        return hit->second;
3826
77.4k
    }
3827
3828
2.06k
    auto sqlRes = d->run(
3829
2.06k
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3830
2.06k
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3831
2.06k
        {sourceAuthName, targetAuthName});
3832
2.06k
    if (sqlRes.empty()) {
3833
445
        sqlRes = d->run(
3834
445
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3835
445
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3836
445
            {sourceAuthName});
3837
445
    }
3838
2.06k
    if (sqlRes.empty()) {
3839
445
        sqlRes = d->run(
3840
445
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3841
445
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3842
445
            {targetAuthName});
3843
445
    }
3844
2.06k
    if (sqlRes.empty()) {
3845
294
        sqlRes = d->run(
3846
294
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3847
294
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3848
294
            {});
3849
294
    }
3850
2.06k
    if (sqlRes.empty()) {
3851
294
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3852
294
        return std::vector<std::string>();
3853
294
    }
3854
1.77k
    auto res = split(sqlRes.front()[0], ',');
3855
1.77k
    d->cacheAllowedAuthorities_[key] = res;
3856
1.77k
    return res;
3857
2.06k
}
3858
3859
// ---------------------------------------------------------------------------
3860
3861
std::list<std::pair<std::string, std::string>>
3862
DatabaseContext::getNonDeprecated(const std::string &tableName,
3863
                                  const std::string &authName,
3864
0
                                  const std::string &code) const {
3865
0
    auto sqlRes =
3866
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3867
0
               "FROM deprecation "
3868
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3869
0
               "AND deprecated_code = ?",
3870
0
               {tableName, authName, code});
3871
0
    std::list<std::pair<std::string, std::string>> res;
3872
0
    for (const auto &row : sqlRes) {
3873
0
        const auto &source = row[2];
3874
0
        if (source == "PROJ") {
3875
0
            const auto &replacement_auth_name = row[0];
3876
0
            const auto &replacement_code = row[1];
3877
0
            res.emplace_back(replacement_auth_name, replacement_code);
3878
0
        }
3879
0
    }
3880
0
    if (!res.empty()) {
3881
0
        return res;
3882
0
    }
3883
0
    for (const auto &row : sqlRes) {
3884
0
        const auto &replacement_auth_name = row[0];
3885
0
        const auto &replacement_code = row[1];
3886
0
        res.emplace_back(replacement_auth_name, replacement_code);
3887
0
    }
3888
0
    return res;
3889
0
}
3890
3891
// ---------------------------------------------------------------------------
3892
3893
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3894
964
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3895
964
    if (cacheAuthNameWithVersion_.empty()) {
3896
198
        const auto sqlRes =
3897
198
            run("SELECT versioned_auth_name, auth_name, version, priority "
3898
198
                "FROM versioned_auth_name_mapping");
3899
198
        for (const auto &row : sqlRes) {
3900
198
            VersionedAuthName van;
3901
198
            van.versionedAuthName = row[0];
3902
198
            van.authName = row[1];
3903
198
            van.version = row[2];
3904
198
            van.priority = atoi(row[3].c_str());
3905
198
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3906
198
        }
3907
198
    }
3908
964
    return cacheAuthNameWithVersion_;
3909
964
}
3910
3911
// ---------------------------------------------------------------------------
3912
3913
// From IAU_2015 returns (IAU,2015)
3914
bool DatabaseContext::getAuthorityAndVersion(
3915
    const std::string &versionedAuthName, std::string &authNameOut,
3916
0
    std::string &versionOut) {
3917
3918
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3919
0
        if (van.versionedAuthName == versionedAuthName) {
3920
0
            authNameOut = van.authName;
3921
0
            versionOut = van.version;
3922
0
            return true;
3923
0
        }
3924
0
    }
3925
0
    return false;
3926
0
}
3927
3928
// ---------------------------------------------------------------------------
3929
3930
// From IAU and 2015, returns IAU_2015
3931
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3932
                                            const std::string &version,
3933
853
                                            std::string &versionedAuthNameOut) {
3934
3935
853
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3936
853
        if (van.authName == authName && van.version == version) {
3937
0
            versionedAuthNameOut = van.versionedAuthName;
3938
0
            return true;
3939
0
        }
3940
853
    }
3941
853
    return false;
3942
853
}
3943
3944
// ---------------------------------------------------------------------------
3945
3946
// From IAU returns IAU_latest, ... IAU_2015
3947
std::vector<std::string>
3948
111
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3949
3950
111
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3951
111
    std::vector<VersionedAuthNamePriority> tmp;
3952
111
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3953
111
        if (van.authName == authName) {
3954
3
            tmp.emplace_back(
3955
3
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3956
3
        }
3957
111
    }
3958
111
    std::vector<std::string> res;
3959
111
    if (!tmp.empty()) {
3960
        // Sort by decreasing priority
3961
3
        std::sort(tmp.begin(), tmp.end(),
3962
3
                  [](const VersionedAuthNamePriority &a,
3963
3
                     const VersionedAuthNamePriority &b) {
3964
0
                      return b.second > a.second;
3965
0
                  });
3966
3
        for (const auto &pair : tmp)
3967
3
            res.emplace_back(pair.first);
3968
3
    }
3969
111
    return res;
3970
111
}
3971
3972
// ---------------------------------------------------------------------------
3973
3974
std::vector<operation::CoordinateOperationNNPtr>
3975
DatabaseContext::getTransformationsForGridName(
3976
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3977
0
    auto sqlRes = databaseContext->d->run(
3978
0
        "SELECT auth_name, code FROM grid_transformation "
3979
0
        "WHERE grid_name = ? OR grid_name IN "
3980
0
        "(SELECT original_grid_name FROM grid_alternatives "
3981
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3982
0
        {gridName, gridName});
3983
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3984
0
    for (const auto &row : sqlRes) {
3985
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3986
0
                             ->createCoordinateOperation(row[1], true));
3987
0
    }
3988
0
    return res;
3989
0
}
3990
3991
// ---------------------------------------------------------------------------
3992
3993
// Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame
3994
// transformation, where they neglect to reverse the sign of the rotation terms.
3995
// Cf https://github.com/OSGeo/PROJ/issues/4170 and
3996
// https://github.com/maptiler/epsg.io/issues/194
3997
// We do that only when we found a valid Coordinate Frame rotation that
3998
// has the same numeric values (and no corresponding Position Vector
3999
// transformation with same values, or Coordinate Frame transformation with
4000
// opposite sign for rotation terms, both are highly unlikely)
4001
bool DatabaseContext::toWGS84AutocorrectWrongValues(
4002
    double &tx, double &ty, double &tz, double &rx, double &ry, double &rz,
4003
4
    double &scale_difference) const {
4004
4
    if (rx == 0 && ry == 0 && rz == 0)
4005
0
        return false;
4006
    // 9606: Position Vector transformation (geog2D domain)
4007
    // 9607: Coordinate Frame rotation (geog2D domain)
4008
4
    std::string sql(
4009
4
        "SELECT DISTINCT method_code "
4010
4
        "FROM helmert_transformation_table WHERE "
4011
4
        "abs(tx - ?) <= 1e-8 * abs(tx) AND "
4012
4
        "abs(ty - ?) <= 1e-8 * abs(ty) AND "
4013
4
        "abs(tz - ?) <= 1e-8 * abs(tz) AND "
4014
4
        "abs(rx - ?) <= 1e-8 * abs(rx) AND "
4015
4
        "abs(ry - ?) <= 1e-8 * abs(ry) AND "
4016
4
        "abs(rz - ?) <= 1e-8 * abs(rz) AND "
4017
4
        "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND "
4018
4
        "method_auth_name = 'EPSG' AND "
4019
4
        "method_code IN (9606, 9607) AND "
4020
4
        "translation_uom_auth_name = 'EPSG' AND "
4021
4
        "translation_uom_code = 9001 AND " // metre
4022
4
        "rotation_uom_auth_name = 'EPSG' AND "
4023
4
        "rotation_uom_code = 9104 AND " // arc-second
4024
4
        "scale_difference_uom_auth_name = 'EPSG' AND "
4025
4
        "scale_difference_uom_code = 9202 AND " // parts per million
4026
4
        "deprecated = 0");
4027
4
    ListOfParams params;
4028
4
    params.emplace_back(tx);
4029
4
    params.emplace_back(ty);
4030
4
    params.emplace_back(tz);
4031
4
    params.emplace_back(rx);
4032
4
    params.emplace_back(ry);
4033
4
    params.emplace_back(rz);
4034
4
    params.emplace_back(scale_difference);
4035
4
    bool bFound9606 = false;
4036
4
    bool bFound9607 = false;
4037
4
    for (const auto &row : d->run(sql, params)) {
4038
0
        if (row[0] == "9606") {
4039
0
            bFound9606 = true;
4040
0
        } else if (row[0] == "9607") {
4041
0
            bFound9607 = true;
4042
0
        }
4043
0
    }
4044
4
    if (bFound9607 && !bFound9606) {
4045
0
        params.clear();
4046
0
        params.emplace_back(tx);
4047
0
        params.emplace_back(ty);
4048
0
        params.emplace_back(tz);
4049
0
        params.emplace_back(-rx);
4050
0
        params.emplace_back(-ry);
4051
0
        params.emplace_back(-rz);
4052
0
        params.emplace_back(scale_difference);
4053
0
        if (d->run(sql, params).empty()) {
4054
0
            if (d->pjCtxt()) {
4055
0
                pj_log(d->pjCtxt(), PJ_LOG_ERROR,
4056
0
                       "Auto-correcting wrong sign of rotation terms of "
4057
0
                       "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to "
4058
0
                       "%s,%s,%s,%s,%s,%s,%s",
4059
0
                       internal::toString(tx).c_str(),
4060
0
                       internal::toString(ty).c_str(),
4061
0
                       internal::toString(tz).c_str(),
4062
0
                       internal::toString(rx).c_str(),
4063
0
                       internal::toString(ry).c_str(),
4064
0
                       internal::toString(rz).c_str(),
4065
0
                       internal::toString(scale_difference).c_str(),
4066
0
                       internal::toString(tx).c_str(),
4067
0
                       internal::toString(ty).c_str(),
4068
0
                       internal::toString(tz).c_str(),
4069
0
                       internal::toString(-rx).c_str(),
4070
0
                       internal::toString(-ry).c_str(),
4071
0
                       internal::toString(-rz).c_str(),
4072
0
                       internal::toString(scale_difference).c_str());
4073
0
            }
4074
0
            rx = -rx;
4075
0
            ry = -ry;
4076
0
            rz = -rz;
4077
0
            return true;
4078
0
        }
4079
0
    }
4080
4
    return false;
4081
4
}
4082
4083
//! @endcond
4084
4085
// ---------------------------------------------------------------------------
4086
4087
//! @cond Doxygen_Suppress
4088
struct AuthorityFactory::Private {
4089
    Private(const DatabaseContextNNPtr &contextIn,
4090
            const std::string &authorityName)
4091
1.01M
        : context_(contextIn), authority_(authorityName) {}
4092
4093
2.95M
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
4094
4095
4.18M
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
4096
4.18M
        return context_;
4097
4.18M
    }
4098
4099
    // cppcheck-suppress functionStatic
4100
1.01M
    void setThis(AuthorityFactoryNNPtr factory) {
4101
1.01M
        thisFactory_ = factory.as_nullable();
4102
1.01M
    }
4103
4104
    // cppcheck-suppress functionStatic
4105
18.5k
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
4106
4107
1.06M
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
4108
1.06M
        if (auth_name == authority_) {
4109
806k
            return NN_NO_CHECK(thisFactory_.lock());
4110
806k
        }
4111
261k
        return AuthorityFactory::create(context_, auth_name);
4112
1.06M
    }
4113
4114
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
4115
                                  bool considerKnownGridsAsAvailable);
4116
4117
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
4118
                                      const std::string &code);
4119
4120
    util::PropertyMap
4121
    createProperties(const std::string &code, const std::string &name,
4122
                     bool deprecated,
4123
                     const std::vector<ObjectDomainNNPtr> &usages);
4124
4125
    util::PropertyMap
4126
    createPropertiesSearchUsages(const std::string &table_name,
4127
                                 const std::string &code,
4128
                                 const std::string &name, bool deprecated);
4129
4130
    util::PropertyMap createPropertiesSearchUsages(
4131
        const std::string &table_name, const std::string &code,
4132
        const std::string &name, bool deprecated, const std::string &remarks);
4133
4134
    SQLResultSet run(const std::string &sql,
4135
                     const ListOfParams &parameters = ListOfParams());
4136
4137
    SQLResultSet runWithCodeParam(const std::string &sql,
4138
                                  const std::string &code);
4139
4140
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
4141
4142
285k
    bool hasAuthorityRestriction() const {
4143
285k
        return !authority_.empty() && authority_ != "any";
4144
285k
    }
4145
4146
    SQLResultSet createProjectedCRSBegin(const std::string &code);
4147
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
4148
                                                 const SQLResultSet &res);
4149
4150
    SQLResultSet createDerivedProjectedCRSBegin(const std::string &code);
4151
    crs::DerivedProjectedCRSNNPtr
4152
    createDerivedProjectedCRSEnd(const std::string &code,
4153
                                 const SQLResultSet &res);
4154
4155
  private:
4156
    DatabaseContextNNPtr context_;
4157
    std::string authority_;
4158
    std::weak_ptr<AuthorityFactory> thisFactory_{};
4159
};
4160
4161
// ---------------------------------------------------------------------------
4162
4163
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
4164
934k
                                            const ListOfParams &parameters) {
4165
934k
    return context()->getPrivate()->run(sql, parameters);
4166
934k
}
4167
4168
// ---------------------------------------------------------------------------
4169
4170
SQLResultSet
4171
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
4172
433k
                                            const std::string &code) {
4173
433k
    return run(sql, {authority(), code});
4174
433k
}
4175
4176
// ---------------------------------------------------------------------------
4177
4178
SQLResultSet
4179
AuthorityFactory::Private::runWithCodeParam(const char *sql,
4180
390k
                                            const std::string &code) {
4181
390k
    return runWithCodeParam(std::string(sql), code);
4182
390k
}
4183
4184
// ---------------------------------------------------------------------------
4185
4186
UnitOfMeasure
4187
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
4188
114k
                                               const std::string &code) {
4189
114k
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
4190
114k
}
4191
4192
// ---------------------------------------------------------------------------
4193
4194
util::PropertyMap AuthorityFactory::Private::createProperties(
4195
    const std::string &code, const std::string &name, bool deprecated,
4196
292k
    const std::vector<ObjectDomainNNPtr> &usages) {
4197
292k
    auto props = util::PropertyMap()
4198
292k
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
4199
292k
                     .set(metadata::Identifier::CODE_KEY, code)
4200
292k
                     .set(common::IdentifiedObject::NAME_KEY, name);
4201
292k
    if (deprecated) {
4202
285
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
4203
285
    }
4204
292k
    if (!usages.empty()) {
4205
4206
279k
        auto array(util::ArrayOfBaseObject::create());
4207
280k
        for (const auto &usage : usages) {
4208
280k
            array->add(usage);
4209
280k
        }
4210
279k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
4211
279k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
4212
279k
    }
4213
292k
    return props;
4214
292k
}
4215
4216
// ---------------------------------------------------------------------------
4217
4218
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4219
    const std::string &table_name, const std::string &code,
4220
281k
    const std::string &name, bool deprecated) {
4221
4222
281k
    SQLResultSet res;
4223
281k
    if (table_name == "geodetic_crs" && code == "4326" &&
4224
3.05k
        authority() == "EPSG") {
4225
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
4226
        // description is super verbose.
4227
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
4228
        // To avoid churn in our WKT2 output, hot patch to the usage of
4229
        // 10.076 and earlier
4230
3.05k
        res = run("SELECT extent.description, extent.south_lat, "
4231
3.05k
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
4232
3.05k
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
4233
3.05k
                  "extent.code = 1262 and scope.code = 1183");
4234
278k
    } else {
4235
278k
        const std::string sql(
4236
278k
            "SELECT extent.description, extent.south_lat, "
4237
278k
            "extent.north_lat, extent.west_lon, extent.east_lon, "
4238
278k
            "scope.scope, "
4239
278k
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
4240
278k
            "AS score "
4241
278k
            "FROM usage "
4242
278k
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
4243
278k
            "usage.extent_code = extent.code "
4244
278k
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
4245
278k
            "usage.scope_code = scope.code "
4246
278k
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
4247
278k
            "object_code = ? AND "
4248
            // We voluntary exclude extent and scope with a specific code
4249
278k
            "NOT (usage.extent_auth_name = 'PROJ' AND "
4250
278k
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
4251
278k
            "NOT (usage.scope_auth_name = 'PROJ' AND "
4252
278k
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4253
278k
            "ORDER BY score, usage.auth_name, usage.code");
4254
278k
        res = run(sql, {table_name, authority(), code});
4255
278k
    }
4256
281k
    std::vector<ObjectDomainNNPtr> usages;
4257
281k
    for (const auto &row : res) {
4258
280k
        try {
4259
280k
            size_t idx = 0;
4260
280k
            const auto &extent_description = row[idx++];
4261
280k
            const auto &south_lat_str = row[idx++];
4262
280k
            const auto &north_lat_str = row[idx++];
4263
280k
            const auto &west_lon_str = row[idx++];
4264
280k
            const auto &east_lon_str = row[idx++];
4265
280k
            const auto &scope = row[idx];
4266
4267
280k
            util::optional<std::string> scopeOpt;
4268
280k
            if (!scope.empty()) {
4269
280k
                scopeOpt = scope;
4270
280k
            }
4271
4272
280k
            metadata::ExtentPtr extent;
4273
280k
            if (south_lat_str.empty()) {
4274
0
                extent = metadata::Extent::create(
4275
0
                             util::optional<std::string>(extent_description),
4276
0
                             {}, {}, {})
4277
0
                             .as_nullable();
4278
280k
            } else {
4279
280k
                double south_lat = c_locale_stod(south_lat_str);
4280
280k
                double north_lat = c_locale_stod(north_lat_str);
4281
280k
                double west_lon = c_locale_stod(west_lon_str);
4282
280k
                double east_lon = c_locale_stod(east_lon_str);
4283
280k
                auto bbox = metadata::GeographicBoundingBox::create(
4284
280k
                    west_lon, south_lat, east_lon, north_lat);
4285
280k
                extent = metadata::Extent::create(
4286
280k
                             util::optional<std::string>(extent_description),
4287
280k
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4288
280k
                             std::vector<metadata::VerticalExtentNNPtr>(),
4289
280k
                             std::vector<metadata::TemporalExtentNNPtr>())
4290
280k
                             .as_nullable();
4291
280k
            }
4292
4293
280k
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4294
280k
        } catch (const std::exception &) {
4295
0
        }
4296
280k
    }
4297
281k
    return createProperties(code, name, deprecated, std::move(usages));
4298
281k
}
4299
4300
// ---------------------------------------------------------------------------
4301
4302
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4303
    const std::string &table_name, const std::string &code,
4304
225k
    const std::string &name, bool deprecated, const std::string &remarks) {
4305
225k
    auto props =
4306
225k
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4307
225k
    if (!remarks.empty())
4308
191k
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4309
225k
    return props;
4310
225k
}
4311
4312
// ---------------------------------------------------------------------------
4313
4314
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4315
    const operation::CoordinateOperationNNPtr &op,
4316
64.8k
    bool considerKnownGridsAsAvailable) {
4317
4318
64.8k
    struct DisableNetwork {
4319
64.8k
        const DatabaseContextNNPtr &m_dbContext;
4320
64.8k
        bool m_old_network_enabled = false;
4321
4322
64.8k
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4323
64.8k
            : m_dbContext(l_context) {
4324
64.8k
            auto ctxt = m_dbContext->d->pjCtxt();
4325
64.8k
            if (ctxt == nullptr) {
4326
0
                ctxt = pj_get_default_ctx();
4327
0
                m_dbContext->d->setPjCtxt(ctxt);
4328
0
            }
4329
64.8k
            m_old_network_enabled =
4330
64.8k
                proj_context_is_network_enabled(ctxt) != FALSE;
4331
64.8k
            if (m_old_network_enabled)
4332
0
                proj_context_set_enable_network(ctxt, false);
4333
64.8k
        }
4334
4335
64.8k
        ~DisableNetwork() {
4336
64.8k
            if (m_old_network_enabled) {
4337
0
                auto ctxt = m_dbContext->d->pjCtxt();
4338
0
                proj_context_set_enable_network(ctxt, true);
4339
0
            }
4340
64.8k
        }
4341
64.8k
    };
4342
4343
64.8k
    auto &l_context = context();
4344
    // Temporarily disable networking as we are only interested in known grids
4345
64.8k
    DisableNetwork disabler(l_context);
4346
4347
64.8k
    for (const auto &gridDesc :
4348
64.8k
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4349
43.6k
        if (!gridDesc.available) {
4350
43.6k
            return true;
4351
43.6k
        }
4352
43.6k
    }
4353
21.1k
    return false;
4354
64.8k
}
4355
4356
//! @endcond
4357
4358
// ---------------------------------------------------------------------------
4359
4360
//! @cond Doxygen_Suppress
4361
1.01M
AuthorityFactory::~AuthorityFactory() = default;
4362
//! @endcond
4363
4364
// ---------------------------------------------------------------------------
4365
4366
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4367
                                   const std::string &authorityName)
4368
1.01M
    : d(std::make_unique<Private>(context, authorityName)) {}
4369
4370
// ---------------------------------------------------------------------------
4371
4372
// clang-format off
4373
/** \brief Instantiate a AuthorityFactory.
4374
 *
4375
 * The authority name might be set to the empty string in the particular case
4376
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4377
 * is called.
4378
 *
4379
 * @param context Context.
4380
 * @param authorityName Authority name.
4381
 * @return new AuthorityFactory.
4382
 */
4383
// clang-format on
4384
4385
AuthorityFactoryNNPtr
4386
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4387
1.01M
                         const std::string &authorityName) {
4388
1.01M
    const auto getFactory = [&context, &authorityName]() {
4389
1.01M
        for (const auto &knownName :
4390
1.55M
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4391
1.55M
            if (ci_equal(authorityName, knownName)) {
4392
821k
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4393
821k
                    context, knownName);
4394
821k
            }
4395
1.55M
        }
4396
197k
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4397
197k
            context, authorityName);
4398
1.01M
    };
4399
1.01M
    auto factory = getFactory();
4400
1.01M
    factory->d->setThis(factory);
4401
1.01M
    return factory;
4402
1.01M
}
4403
4404
// ---------------------------------------------------------------------------
4405
4406
/** \brief Returns the database context. */
4407
898k
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4408
898k
    return d->context();
4409
898k
}
4410
4411
// ---------------------------------------------------------------------------
4412
4413
//! @cond Doxygen_Suppress
4414
AuthorityFactory::CRSInfo::CRSInfo()
4415
0
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4416
0
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4417
0
      north_lat_degree{}, areaName{}, projectionMethodName{},
4418
0
      celestialBodyName{} {}
4419
//! @endcond
4420
4421
// ---------------------------------------------------------------------------
4422
4423
/** \brief Returns an arbitrary object from a code.
4424
 *
4425
 * The returned object will typically be an instance of Datum,
4426
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4427
 * the object is know at compile time, it is recommended to invoke the most
4428
 * precise method instead of this one (for example
4429
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4430
 * if the caller know he is asking for a coordinate reference system).
4431
 *
4432
 * If there are several objects with the same code, a FactoryException is
4433
 * thrown.
4434
 *
4435
 * @param code Object code allocated by authority. (e.g. "4326")
4436
 * @return object.
4437
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4438
 * @throw FactoryException in case of other errors.
4439
 */
4440
4441
util::BaseObjectNNPtr
4442
0
AuthorityFactory::createObject(const std::string &code) const {
4443
4444
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4445
0
                                   "WHERE auth_name = ? AND code = ?",
4446
0
                                   code);
4447
0
    if (res.empty()) {
4448
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4449
0
    }
4450
0
    if (res.size() != 1) {
4451
0
        std::string msg(
4452
0
            "More than one object matching specified code. Objects found in ");
4453
0
        bool first = true;
4454
0
        for (const auto &row : res) {
4455
0
            if (!first)
4456
0
                msg += ", ";
4457
0
            msg += row[0];
4458
0
            first = false;
4459
0
        }
4460
0
        throw FactoryException(msg);
4461
0
    }
4462
0
    const auto &first_row = res.front();
4463
0
    const auto &table_name = first_row[0];
4464
0
    const auto &type = first_row[1];
4465
0
    if (table_name == "extent") {
4466
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4467
0
            createExtent(code));
4468
0
    }
4469
0
    if (table_name == "unit_of_measure") {
4470
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4471
0
            createUnitOfMeasure(code));
4472
0
    }
4473
0
    if (table_name == "prime_meridian") {
4474
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4475
0
            createPrimeMeridian(code));
4476
0
    }
4477
0
    if (table_name == "ellipsoid") {
4478
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4479
0
            createEllipsoid(code));
4480
0
    }
4481
0
    if (table_name == "geodetic_datum") {
4482
0
        if (type == "ensemble") {
4483
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4484
0
                createDatumEnsemble(code, table_name));
4485
0
        }
4486
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4487
0
            createGeodeticDatum(code));
4488
0
    }
4489
0
    if (table_name == "vertical_datum") {
4490
0
        if (type == "ensemble") {
4491
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4492
0
                createDatumEnsemble(code, table_name));
4493
0
        }
4494
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4495
0
            createVerticalDatum(code));
4496
0
    }
4497
0
    if (table_name == "engineering_datum") {
4498
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4499
0
            createEngineeringDatum(code));
4500
0
    }
4501
0
    if (table_name == "geodetic_crs") {
4502
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4503
0
            createGeodeticCRS(code));
4504
0
    }
4505
0
    if (table_name == "vertical_crs") {
4506
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4507
0
            createVerticalCRS(code));
4508
0
    }
4509
0
    if (table_name == "projected_crs") {
4510
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4511
0
            createProjectedCRS(code));
4512
0
    }
4513
0
    if (table_name == "derived_projected_crs") {
4514
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4515
0
            createDerivedProjectedCRS(code));
4516
0
    }
4517
0
    if (table_name == "compound_crs") {
4518
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4519
0
            createCompoundCRS(code));
4520
0
    }
4521
0
    if (table_name == "engineering_crs") {
4522
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4523
0
            createEngineeringCRS(code));
4524
0
    }
4525
0
    if (table_name == "conversion") {
4526
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4527
0
            createConversion(code));
4528
0
    }
4529
0
    if (table_name == "helmert_transformation" ||
4530
0
        table_name == "grid_transformation" ||
4531
0
        table_name == "other_transformation" ||
4532
0
        table_name == "concatenated_operation") {
4533
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4534
0
            createCoordinateOperation(code, false));
4535
0
    }
4536
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4537
0
}
4538
4539
// ---------------------------------------------------------------------------
4540
4541
//! @cond Doxygen_Suppress
4542
static FactoryException buildFactoryException(const char *type,
4543
                                              const std::string &authName,
4544
                                              const std::string &code,
4545
0
                                              const std::exception &ex) {
4546
0
    return FactoryException(std::string("cannot build ") + type + " " +
4547
0
                            authName + ":" + code + ": " + ex.what());
4548
0
}
4549
//! @endcond
4550
4551
// ---------------------------------------------------------------------------
4552
4553
/** \brief Returns a metadata::Extent from the specified code.
4554
 *
4555
 * @param code Object code allocated by authority.
4556
 * @return object.
4557
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4558
 * @throw FactoryException in case of other errors.
4559
 */
4560
4561
metadata::ExtentNNPtr
4562
0
AuthorityFactory::createExtent(const std::string &code) const {
4563
0
    const auto cacheKey(d->authority() + code);
4564
0
    {
4565
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4566
0
        if (extent) {
4567
0
            return NN_NO_CHECK(extent);
4568
0
        }
4569
0
    }
4570
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4571
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4572
0
    auto res = d->runWithCodeParam(sql, code);
4573
0
    if (res.empty()) {
4574
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4575
0
                                           code);
4576
0
    }
4577
0
    try {
4578
0
        const auto &row = res.front();
4579
0
        const auto &description = row[0];
4580
0
        if (row[1].empty()) {
4581
0
            auto extent = metadata::Extent::create(
4582
0
                util::optional<std::string>(description), {}, {}, {});
4583
0
            d->context()->d->cache(cacheKey, extent);
4584
0
            return extent;
4585
0
        }
4586
0
        double south_lat = c_locale_stod(row[1]);
4587
0
        double north_lat = c_locale_stod(row[2]);
4588
0
        double west_lon = c_locale_stod(row[3]);
4589
0
        double east_lon = c_locale_stod(row[4]);
4590
0
        auto bbox = metadata::GeographicBoundingBox::create(
4591
0
            west_lon, south_lat, east_lon, north_lat);
4592
4593
0
        auto extent = metadata::Extent::create(
4594
0
            util::optional<std::string>(description),
4595
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4596
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4597
0
            std::vector<metadata::TemporalExtentNNPtr>());
4598
0
        d->context()->d->cache(cacheKey, extent);
4599
0
        return extent;
4600
4601
0
    } catch (const std::exception &ex) {
4602
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4603
0
    }
4604
0
}
4605
4606
// ---------------------------------------------------------------------------
4607
4608
/** \brief Returns a common::UnitOfMeasure from the specified code.
4609
 *
4610
 * @param code Object code allocated by authority.
4611
 * @return object.
4612
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4613
 * @throw FactoryException in case of other errors.
4614
 */
4615
4616
UnitOfMeasureNNPtr
4617
114k
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4618
114k
    const auto cacheKey(d->authority() + code);
4619
114k
    {
4620
114k
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4621
114k
        if (uom) {
4622
99.3k
            return NN_NO_CHECK(uom);
4623
99.3k
        }
4624
114k
    }
4625
15.5k
    auto res = d->context()->d->run(
4626
15.5k
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4627
15.5k
        "auth_name = ? AND code = ?",
4628
15.5k
        {d->authority(), code}, true);
4629
15.5k
    if (res.empty()) {
4630
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4631
0
                                           d->authority(), code);
4632
0
    }
4633
15.5k
    try {
4634
15.5k
        const auto &row = res.front();
4635
15.5k
        const auto &name =
4636
15.5k
            (row[0] == "degree (supplier to define representation)")
4637
15.5k
                ? UnitOfMeasure::DEGREE.name()
4638
15.5k
                : row[0];
4639
15.5k
        double conv_factor = (code == "9107" || code == "9108")
4640
15.5k
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4641
15.5k
                                 : c_locale_stod(row[1]);
4642
15.5k
        constexpr double EPS = 1e-10;
4643
15.5k
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4644
15.5k
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4645
7.35k
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4646
7.35k
        }
4647
15.5k
        if (std::fabs(conv_factor -
4648
15.5k
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4649
15.5k
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4650
1.01k
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4651
1.01k
        }
4652
15.5k
        const auto &type_str = row[2];
4653
15.5k
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4654
15.5k
        if (type_str == "length")
4655
4.28k
            unitType = UnitOfMeasure::Type::LINEAR;
4656
11.2k
        else if (type_str == "angle")
4657
9.22k
            unitType = UnitOfMeasure::Type::ANGULAR;
4658
2.02k
        else if (type_str == "scale")
4659
1.89k
            unitType = UnitOfMeasure::Type::SCALE;
4660
136
        else if (type_str == "time")
4661
136
            unitType = UnitOfMeasure::Type::TIME;
4662
15.5k
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4663
15.5k
            name, conv_factor, unitType, d->authority(), code);
4664
15.5k
        d->context()->d->cache(cacheKey, uom);
4665
15.5k
        return uom;
4666
15.5k
    } catch (const std::exception &ex) {
4667
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4668
0
                                    ex);
4669
0
    }
4670
15.5k
}
4671
4672
// ---------------------------------------------------------------------------
4673
4674
//! @cond Doxygen_Suppress
4675
static double normalizeMeasure(const std::string &uom_code,
4676
                               const std::string &value,
4677
23.3k
                               std::string &normalized_uom_code) {
4678
23.3k
    if (uom_code == "9110") // DDD.MMSSsss.....
4679
3.21k
    {
4680
3.21k
        double normalized_value = c_locale_stod(value);
4681
3.21k
        std::ostringstream buffer;
4682
3.21k
        buffer.imbue(std::locale::classic());
4683
3.21k
        constexpr size_t precision = 12;
4684
3.21k
        buffer << std::fixed << std::setprecision(precision)
4685
3.21k
               << normalized_value;
4686
3.21k
        auto formatted = buffer.str();
4687
3.21k
        size_t dotPos = formatted.find('.');
4688
3.21k
        assert(dotPos + 1 + precision == formatted.size());
4689
3.21k
        auto minutes = formatted.substr(dotPos + 1, 2);
4690
3.21k
        auto seconds = formatted.substr(dotPos + 3);
4691
3.21k
        assert(seconds.size() == precision - 2);
4692
3.21k
        normalized_value =
4693
3.21k
            (normalized_value < 0 ? -1.0 : 1.0) *
4694
3.21k
            (std::floor(std::fabs(normalized_value)) +
4695
3.21k
             c_locale_stod(minutes) / 60. +
4696
3.21k
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4697
3.21k
                 3600.);
4698
3.21k
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4699
        /* coverity[overflow_sink] */
4700
3.21k
        return normalized_value;
4701
20.1k
    } else {
4702
20.1k
        normalized_uom_code = uom_code;
4703
20.1k
        return c_locale_stod(value);
4704
20.1k
    }
4705
23.3k
}
4706
//! @endcond
4707
4708
// ---------------------------------------------------------------------------
4709
4710
/** \brief Returns a datum::PrimeMeridian from the specified code.
4711
 *
4712
 * @param code Object code allocated by authority.
4713
 * @return object.
4714
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4715
 * @throw FactoryException in case of other errors.
4716
 */
4717
4718
datum::PrimeMeridianNNPtr
4719
42.7k
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4720
42.7k
    const auto cacheKey(d->authority() + code);
4721
42.7k
    {
4722
42.7k
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4723
42.7k
        if (pm) {
4724
38.6k
            return NN_NO_CHECK(pm);
4725
38.6k
        }
4726
42.7k
    }
4727
4.10k
    auto res = d->runWithCodeParam(
4728
4.10k
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4729
4.10k
        "prime_meridian WHERE "
4730
4.10k
        "auth_name = ? AND code = ?",
4731
4.10k
        code);
4732
4.10k
    if (res.empty()) {
4733
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4734
0
                                           d->authority(), code);
4735
0
    }
4736
4.10k
    try {
4737
4.10k
        const auto &row = res.front();
4738
4.10k
        const auto &name = row[0];
4739
4.10k
        const auto &longitude = row[1];
4740
4.10k
        const auto &uom_auth_name = row[2];
4741
4.10k
        const auto &uom_code = row[3];
4742
4.10k
        const bool deprecated = row[4] == "1";
4743
4744
4.10k
        std::string normalized_uom_code(uom_code);
4745
4.10k
        const double normalized_value =
4746
4.10k
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4747
4748
4.10k
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4749
4.10k
        auto props = d->createProperties(code, name, deprecated, {});
4750
4.10k
        auto pm = datum::PrimeMeridian::create(
4751
4.10k
            props, common::Angle(normalized_value, uom));
4752
4.10k
        d->context()->d->cache(cacheKey, pm);
4753
4.10k
        return pm;
4754
4.10k
    } catch (const std::exception &ex) {
4755
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4756
0
    }
4757
4.10k
}
4758
4759
// ---------------------------------------------------------------------------
4760
4761
/** \brief Identify a celestial body from an approximate radius.
4762
 *
4763
 * @param semi_major_axis Approximate semi-major axis.
4764
 * @param tolerance Relative error allowed.
4765
 * @return celestial body name if one single match found.
4766
 * @throw FactoryException in case of error.
4767
 */
4768
4769
std::string
4770
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4771
1.56k
                                                double tolerance) const {
4772
1.56k
    auto res =
4773
1.56k
        d->run("SELECT DISTINCT name, "
4774
1.56k
               "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error "
4775
1.56k
               "FROM celestial_body WHERE rel_error <= ? "
4776
1.56k
               "ORDER BY rel_error, name",
4777
1.56k
               {semi_major_axis, tolerance});
4778
1.56k
    if (res.empty()) {
4779
1.46k
        throw FactoryException("no match found");
4780
1.46k
    }
4781
102
    constexpr int IDX_NAME = 0;
4782
102
    if (res.size() > 1) {
4783
14
        constexpr int IDX_REL_ERROR = 1;
4784
        // If the first object has a relative error of 0 and the next one
4785
        // a non-zero error, then use the first one.
4786
14
        if (res.front()[IDX_REL_ERROR] == "0" &&
4787
0
            (*std::next(res.begin()))[IDX_REL_ERROR] != "0") {
4788
0
            return res.front()[IDX_NAME];
4789
0
        }
4790
28
        for (const auto &row : res) {
4791
28
            if (row[IDX_NAME] != res.front()[IDX_NAME]) {
4792
12
                throw FactoryException("more than one match found");
4793
12
            }
4794
28
        }
4795
14
    }
4796
90
    return res.front()[IDX_NAME];
4797
102
}
4798
4799
// ---------------------------------------------------------------------------
4800
4801
/** \brief Returns a datum::Ellipsoid from the specified code.
4802
 *
4803
 * @param code Object code allocated by authority.
4804
 * @return object.
4805
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4806
 * @throw FactoryException in case of other errors.
4807
 */
4808
4809
datum::EllipsoidNNPtr
4810
43.0k
AuthorityFactory::createEllipsoid(const std::string &code) const {
4811
43.0k
    const auto cacheKey(d->authority() + code);
4812
43.0k
    {
4813
43.0k
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4814
43.0k
        if (ellps) {
4815
35.6k
            return NN_NO_CHECK(ellps);
4816
35.6k
        }
4817
43.0k
    }
4818
7.34k
    auto res = d->runWithCodeParam(
4819
7.34k
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4820
7.34k
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4821
7.34k
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4822
7.34k
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4823
7.34k
        "ellipsoid JOIN celestial_body "
4824
7.34k
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4825
7.34k
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4826
7.34k
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4827
7.34k
        code);
4828
7.34k
    if (res.empty()) {
4829
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4830
0
                                           d->authority(), code);
4831
0
    }
4832
7.34k
    try {
4833
7.34k
        const auto &row = res.front();
4834
7.34k
        const auto &name = row[0];
4835
7.34k
        const auto &semi_major_axis_str = row[1];
4836
7.34k
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4837
7.34k
        const auto &uom_auth_name = row[2];
4838
7.34k
        const auto &uom_code = row[3];
4839
7.34k
        const auto &inv_flattening_str = row[4];
4840
7.34k
        const auto &semi_minor_axis_str = row[5];
4841
7.34k
        const auto &body = row[6];
4842
7.34k
        const bool deprecated = row[7] == "1";
4843
7.34k
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4844
7.34k
        auto props = d->createProperties(code, name, deprecated, {});
4845
7.34k
        if (!inv_flattening_str.empty()) {
4846
6.41k
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4847
6.41k
                props, common::Length(semi_major_axis, uom),
4848
6.41k
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4849
6.41k
            d->context()->d->cache(cacheKey, ellps);
4850
6.41k
            return ellps;
4851
6.41k
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4852
62
            auto ellps = datum::Ellipsoid::createSphere(
4853
62
                props, common::Length(semi_major_axis, uom), body);
4854
62
            d->context()->d->cache(cacheKey, ellps);
4855
62
            return ellps;
4856
867
        } else {
4857
867
            auto ellps = datum::Ellipsoid::createTwoAxis(
4858
867
                props, common::Length(semi_major_axis, uom),
4859
867
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4860
867
            d->context()->d->cache(cacheKey, ellps);
4861
867
            return ellps;
4862
867
        }
4863
7.34k
    } catch (const std::exception &ex) {
4864
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4865
0
    }
4866
7.34k
}
4867
4868
// ---------------------------------------------------------------------------
4869
4870
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4871
 *
4872
 * @param code Object code allocated by authority.
4873
 * @return object.
4874
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4875
 * @throw FactoryException in case of other errors.
4876
 */
4877
4878
datum::GeodeticReferenceFrameNNPtr
4879
336k
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4880
4881
336k
    datum::GeodeticReferenceFramePtr datum;
4882
336k
    datum::DatumEnsemblePtr datumEnsemble;
4883
336k
    constexpr bool turnEnsembleAsDatum = true;
4884
336k
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4885
336k
                                  turnEnsembleAsDatum);
4886
336k
    return NN_NO_CHECK(datum);
4887
336k
}
4888
4889
// ---------------------------------------------------------------------------
4890
4891
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4892
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4893
379k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4894
379k
    const auto cacheKey(d->authority() + code);
4895
379k
    {
4896
379k
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4897
379k
        if (outDatumEnsemble) {
4898
327k
            if (!turnEnsembleAsDatum)
4899
23.9k
                return;
4900
303k
            outDatumEnsemble = nullptr;
4901
303k
        }
4902
355k
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4903
355k
        if (outDatum) {
4904
309k
            return;
4905
309k
        }
4906
355k
    }
4907
46.0k
    auto res = d->runWithCodeParam(
4908
46.0k
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4909
46.0k
        "prime_meridian_auth_name, prime_meridian_code, "
4910
46.0k
        "publication_date, frame_reference_epoch, "
4911
46.0k
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4912
46.0k
        "FROM geodetic_datum "
4913
46.0k
        "WHERE "
4914
46.0k
        "auth_name = ? AND code = ?",
4915
46.0k
        code);
4916
46.0k
    if (res.empty()) {
4917
25
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4918
25
                                           d->authority(), code);
4919
25
    }
4920
46.0k
    try {
4921
46.0k
        const auto &row = res.front();
4922
46.0k
        const auto &name = row[0];
4923
46.0k
        const auto &ellipsoid_auth_name = row[1];
4924
46.0k
        const auto &ellipsoid_code = row[2];
4925
46.0k
        const auto &prime_meridian_auth_name = row[3];
4926
46.0k
        const auto &prime_meridian_code = row[4];
4927
46.0k
        const auto &publication_date = row[5];
4928
46.0k
        const auto &frame_reference_epoch = row[6];
4929
46.0k
        const auto &ensemble_accuracy = row[7];
4930
46.0k
        const auto &anchor = row[8];
4931
46.0k
        const auto &anchor_epoch = row[9];
4932
46.0k
        const bool deprecated = row[10] == "1";
4933
4934
46.0k
        std::string massagedName;
4935
46.0k
        if (turnEnsembleAsDatum) {
4936
35.5k
            massagedName =
4937
35.5k
                datum::DatumEnsemble::ensembleNameToNonEnsembleName(name);
4938
35.5k
        }
4939
46.0k
        if (massagedName.empty()) {
4940
43.1k
            massagedName = name;
4941
43.1k
        }
4942
46.0k
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4943
46.0k
                                                     massagedName, deprecated);
4944
4945
46.0k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4946
3.25k
            auto resMembers =
4947
3.25k
                d->run("SELECT member_auth_name, member_code FROM "
4948
3.25k
                       "geodetic_datum_ensemble_member WHERE "
4949
3.25k
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4950
3.25k
                       "ORDER BY sequence",
4951
3.25k
                       {d->authority(), code});
4952
4953
3.25k
            std::vector<datum::DatumNNPtr> members;
4954
32.7k
            for (const auto &memberRow : resMembers) {
4955
32.7k
                members.push_back(
4956
32.7k
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4957
32.7k
            }
4958
3.25k
            auto datumEnsemble = datum::DatumEnsemble::create(
4959
3.25k
                props, std::move(members),
4960
3.25k
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4961
3.25k
            d->context()->d->cache(cacheKey, datumEnsemble);
4962
3.25k
            outDatumEnsemble = datumEnsemble.as_nullable();
4963
42.7k
        } else {
4964
42.7k
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4965
42.7k
                                 ->createEllipsoid(ellipsoid_code);
4966
42.7k
            auto pm = d->createFactory(prime_meridian_auth_name)
4967
42.7k
                          ->createPrimeMeridian(prime_meridian_code);
4968
4969
42.7k
            auto anchorOpt = util::optional<std::string>();
4970
42.7k
            if (!anchor.empty())
4971
52
                anchorOpt = anchor;
4972
42.7k
            if (!publication_date.empty()) {
4973
36.4k
                props.set("PUBLICATION_DATE", publication_date);
4974
36.4k
            }
4975
42.7k
            if (!anchor_epoch.empty()) {
4976
6.78k
                props.set("ANCHOR_EPOCH", anchor_epoch);
4977
6.78k
            }
4978
42.7k
            auto datum = frame_reference_epoch.empty()
4979
42.7k
                             ? datum::GeodeticReferenceFrame::create(
4980
15.5k
                                   props, ellipsoid, anchorOpt, pm)
4981
42.7k
                             : util::nn_static_pointer_cast<
4982
27.1k
                                   datum::GeodeticReferenceFrame>(
4983
27.1k
                                   datum::DynamicGeodeticReferenceFrame::create(
4984
27.1k
                                       props, ellipsoid, anchorOpt, pm,
4985
27.1k
                                       common::Measure(
4986
27.1k
                                           c_locale_stod(frame_reference_epoch),
4987
27.1k
                                           common::UnitOfMeasure::YEAR),
4988
27.1k
                                       util::optional<std::string>()));
4989
42.7k
            d->context()->d->cache(cacheKey, datum);
4990
42.7k
            outDatum = datum.as_nullable();
4991
42.7k
        }
4992
46.0k
    } catch (const std::exception &ex) {
4993
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
4994
0
                                    code, ex);
4995
0
    }
4996
46.0k
}
4997
4998
// ---------------------------------------------------------------------------
4999
5000
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
5001
 *
5002
 * @param code Object code allocated by authority.
5003
 * @return object.
5004
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5005
 * @throw FactoryException in case of other errors.
5006
 */
5007
5008
datum::VerticalReferenceFrameNNPtr
5009
102
AuthorityFactory::createVerticalDatum(const std::string &code) const {
5010
102
    datum::VerticalReferenceFramePtr datum;
5011
102
    datum::DatumEnsemblePtr datumEnsemble;
5012
102
    constexpr bool turnEnsembleAsDatum = true;
5013
102
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
5014
102
                                  turnEnsembleAsDatum);
5015
102
    return NN_NO_CHECK(datum);
5016
102
}
5017
5018
// ---------------------------------------------------------------------------
5019
5020
void AuthorityFactory::createVerticalDatumOrEnsemble(
5021
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
5022
1.35k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
5023
1.35k
    auto res =
5024
1.35k
        d->runWithCodeParam("SELECT name, publication_date, "
5025
1.35k
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
5026
1.35k
                            "anchor_epoch, deprecated FROM "
5027
1.35k
                            "vertical_datum WHERE auth_name = ? AND code = ?",
5028
1.35k
                            code);
5029
1.35k
    if (res.empty()) {
5030
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
5031
0
                                           d->authority(), code);
5032
0
    }
5033
1.35k
    try {
5034
1.35k
        const auto &row = res.front();
5035
1.35k
        const auto &name = row[0];
5036
1.35k
        const auto &publication_date = row[1];
5037
1.35k
        const auto &frame_reference_epoch = row[2];
5038
1.35k
        const auto &ensemble_accuracy = row[3];
5039
1.35k
        const auto &anchor = row[4];
5040
1.35k
        const auto &anchor_epoch = row[5];
5041
1.35k
        const bool deprecated = row[6] == "1";
5042
1.35k
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
5043
1.35k
                                                     name, deprecated);
5044
1.35k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
5045
24
            auto resMembers =
5046
24
                d->run("SELECT member_auth_name, member_code FROM "
5047
24
                       "vertical_datum_ensemble_member WHERE "
5048
24
                       "ensemble_auth_name = ? AND ensemble_code = ? "
5049
24
                       "ORDER BY sequence",
5050
24
                       {d->authority(), code});
5051
5052
24
            std::vector<datum::DatumNNPtr> members;
5053
102
            for (const auto &memberRow : resMembers) {
5054
102
                members.push_back(
5055
102
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5056
102
            }
5057
24
            auto datumEnsemble = datum::DatumEnsemble::create(
5058
24
                props, std::move(members),
5059
24
                metadata::PositionalAccuracy::create(ensemble_accuracy));
5060
24
            outDatumEnsemble = datumEnsemble.as_nullable();
5061
1.33k
        } else {
5062
1.33k
            if (!publication_date.empty()) {
5063
528
                props.set("PUBLICATION_DATE", publication_date);
5064
528
            }
5065
1.33k
            if (!anchor_epoch.empty()) {
5066
16
                props.set("ANCHOR_EPOCH", anchor_epoch);
5067
16
            }
5068
1.33k
            if (d->authority() == "ESRI" &&
5069
609
                starts_with(code, "from_geogdatum_")) {
5070
594
                props.set("VERT_DATUM_TYPE", "2002");
5071
594
            }
5072
1.33k
            auto anchorOpt = util::optional<std::string>();
5073
1.33k
            if (!anchor.empty())
5074
0
                anchorOpt = anchor;
5075
1.33k
            if (frame_reference_epoch.empty()) {
5076
1.32k
                outDatum =
5077
1.32k
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
5078
1.32k
                        .as_nullable();
5079
1.32k
            } else {
5080
5
                outDatum =
5081
5
                    datum::DynamicVerticalReferenceFrame::create(
5082
5
                        props, anchorOpt,
5083
5
                        util::optional<datum::RealizationMethod>(),
5084
5
                        common::Measure(c_locale_stod(frame_reference_epoch),
5085
5
                                        common::UnitOfMeasure::YEAR),
5086
5
                        util::optional<std::string>())
5087
5
                        .as_nullable();
5088
5
            }
5089
1.33k
        }
5090
1.35k
    } catch (const std::exception &ex) {
5091
0
        throw buildFactoryException("vertical reference frame", d->authority(),
5092
0
                                    code, ex);
5093
0
    }
5094
1.35k
}
5095
5096
// ---------------------------------------------------------------------------
5097
5098
/** \brief Returns a datum::EngineeringDatum from the specified code.
5099
 *
5100
 * @param code Object code allocated by authority.
5101
 * @return object.
5102
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5103
 * @throw FactoryException in case of other errors.
5104
 * @since 9.6
5105
 */
5106
5107
datum::EngineeringDatumNNPtr
5108
27
AuthorityFactory::createEngineeringDatum(const std::string &code) const {
5109
27
    auto res = d->runWithCodeParam(
5110
27
        "SELECT name, publication_date, "
5111
27
        "anchor, anchor_epoch, deprecated FROM "
5112
27
        "engineering_datum WHERE auth_name = ? AND code = ?",
5113
27
        code);
5114
27
    if (res.empty()) {
5115
0
        throw NoSuchAuthorityCodeException("engineering datum not found",
5116
0
                                           d->authority(), code);
5117
0
    }
5118
27
    try {
5119
27
        const auto &row = res.front();
5120
27
        const auto &name = row[0];
5121
27
        const auto &publication_date = row[1];
5122
27
        const auto &anchor = row[2];
5123
27
        const auto &anchor_epoch = row[3];
5124
27
        const bool deprecated = row[4] == "1";
5125
27
        auto props = d->createPropertiesSearchUsages("engineering_datum", code,
5126
27
                                                     name, deprecated);
5127
5128
27
        if (!publication_date.empty()) {
5129
4
            props.set("PUBLICATION_DATE", publication_date);
5130
4
        }
5131
27
        if (!anchor_epoch.empty()) {
5132
0
            props.set("ANCHOR_EPOCH", anchor_epoch);
5133
0
        }
5134
27
        auto anchorOpt = util::optional<std::string>();
5135
27
        if (!anchor.empty())
5136
0
            anchorOpt = anchor;
5137
27
        return datum::EngineeringDatum::create(props, anchorOpt);
5138
27
    } catch (const std::exception &ex) {
5139
0
        throw buildFactoryException("engineering datum", d->authority(), code,
5140
0
                                    ex);
5141
0
    }
5142
27
}
5143
5144
// ---------------------------------------------------------------------------
5145
5146
/** \brief Returns a datum::DatumEnsemble from the specified code.
5147
 *
5148
 * @param code Object code allocated by authority.
5149
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
5150
 * @return object.
5151
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5152
 * @throw FactoryException in case of other errors.
5153
 */
5154
5155
datum::DatumEnsembleNNPtr
5156
AuthorityFactory::createDatumEnsemble(const std::string &code,
5157
0
                                      const std::string &type) const {
5158
0
    auto res = d->run(
5159
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
5160
0
        "geodetic_datum WHERE "
5161
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
5162
0
        "UNION ALL "
5163
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
5164
0
        "vertical_datum WHERE "
5165
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
5166
0
        {d->authority(), code, d->authority(), code});
5167
0
    if (res.empty()) {
5168
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
5169
0
                                           d->authority(), code);
5170
0
    }
5171
0
    for (const auto &row : res) {
5172
0
        const std::string &gotType = row[0];
5173
0
        const std::string &name = row[1];
5174
0
        const std::string &ensembleAccuracy = row[2];
5175
0
        const bool deprecated = row[3] == "1";
5176
0
        if (type.empty() || type == gotType) {
5177
0
            auto resMembers =
5178
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
5179
0
                           "_ensemble_member WHERE "
5180
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
5181
0
                           "ORDER BY sequence",
5182
0
                       {d->authority(), code});
5183
5184
0
            std::vector<datum::DatumNNPtr> members;
5185
0
            for (const auto &memberRow : resMembers) {
5186
0
                members.push_back(
5187
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5188
0
            }
5189
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
5190
0
                                                         deprecated);
5191
0
            return datum::DatumEnsemble::create(
5192
0
                props, std::move(members),
5193
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
5194
0
        }
5195
0
    }
5196
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
5197
0
                                       d->authority(), code);
5198
0
}
5199
5200
// ---------------------------------------------------------------------------
5201
5202
/** \brief Returns a datum::Datum from the specified code.
5203
 *
5204
 * @param code Object code allocated by authority.
5205
 * @return object.
5206
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5207
 * @throw FactoryException in case of other errors.
5208
 */
5209
5210
32.8k
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
5211
32.8k
    auto res = d->run(
5212
32.8k
        "SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
5213
32.8k
        "auth_name = ? AND code = ? "
5214
32.8k
        "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
5215
32.8k
        "auth_name = ? AND code = ? "
5216
32.8k
        "UNION ALL SELECT 'engineering_datum' FROM engineering_datum "
5217
32.8k
        "WHERE "
5218
32.8k
        "auth_name = ? AND code = ?",
5219
32.8k
        {d->authority(), code, d->authority(), code, d->authority(), code});
5220
32.8k
    if (res.empty()) {
5221
0
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
5222
0
                                           code);
5223
0
    }
5224
32.8k
    const auto &type = res.front()[0];
5225
32.8k
    if (type == "geodetic_datum") {
5226
32.7k
        return createGeodeticDatum(code);
5227
32.7k
    }
5228
102
    if (type == "vertical_datum") {
5229
102
        return createVerticalDatum(code);
5230
102
    }
5231
0
    return createEngineeringDatum(code);
5232
102
}
5233
5234
// ---------------------------------------------------------------------------
5235
5236
//! @cond Doxygen_Suppress
5237
80
static cs::MeridianPtr createMeridian(const std::string &val) {
5238
80
    try {
5239
80
        const std::string degW(std::string("\xC2\xB0") + "W");
5240
80
        if (ends_with(val, degW)) {
5241
15
            return cs::Meridian::create(common::Angle(
5242
15
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
5243
15
        }
5244
65
        const std::string degE(std::string("\xC2\xB0") + "E");
5245
65
        if (ends_with(val, degE)) {
5246
65
            return cs::Meridian::create(common::Angle(
5247
65
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
5248
65
        }
5249
65
    } catch (const std::exception &) {
5250
0
    }
5251
0
    return nullptr;
5252
80
}
5253
//! @endcond
5254
5255
// ---------------------------------------------------------------------------
5256
5257
/** \brief Returns a cs::CoordinateSystem from the specified code.
5258
 *
5259
 * @param code Object code allocated by authority.
5260
 * @return object.
5261
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5262
 * @throw FactoryException in case of other errors.
5263
 */
5264
5265
cs::CoordinateSystemNNPtr
5266
46.3k
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
5267
46.3k
    const auto cacheKey(d->authority() + code);
5268
46.3k
    {
5269
46.3k
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
5270
46.3k
        if (cs) {
5271
30.1k
            return NN_NO_CHECK(cs);
5272
30.1k
        }
5273
46.3k
    }
5274
16.2k
    auto res = d->runWithCodeParam(
5275
16.2k
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
5276
16.2k
        "cs.type FROM "
5277
16.2k
        "axis LEFT JOIN coordinate_system cs ON "
5278
16.2k
        "axis.coordinate_system_auth_name = cs.auth_name AND "
5279
16.2k
        "axis.coordinate_system_code = cs.code WHERE "
5280
16.2k
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
5281
16.2k
        "BY coordinate_system_order",
5282
16.2k
        code);
5283
16.2k
    if (res.empty()) {
5284
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
5285
0
                                           d->authority(), code);
5286
0
    }
5287
5288
16.2k
    const auto &csType = res.front()[5];
5289
16.2k
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
5290
39.8k
    for (const auto &row : res) {
5291
39.8k
        const auto &name = row[0];
5292
39.8k
        const auto &abbrev = row[1];
5293
39.8k
        const auto &orientation = row[2];
5294
39.8k
        const auto &uom_auth_name = row[3];
5295
39.8k
        const auto &uom_code = row[4];
5296
39.8k
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
5297
0
            throw FactoryException("no unit of measure for an axis is only "
5298
0
                                   "supported for ordinatal CS");
5299
0
        }
5300
39.8k
        auto uom = uom_auth_name.empty()
5301
39.8k
                       ? common::UnitOfMeasure::NONE
5302
39.8k
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
5303
39.8k
        auto props =
5304
39.8k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
5305
39.8k
        const cs::AxisDirection *direction =
5306
39.8k
            cs::AxisDirection::valueOf(orientation);
5307
39.8k
        cs::MeridianPtr meridian;
5308
39.8k
        if (direction == nullptr) {
5309
80
            if (orientation == "Geocentre > equator/0"
5310
80
                               "\xC2\xB0"
5311
80
                               "E") {
5312
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
5313
80
            } else if (orientation == "Geocentre > equator/90"
5314
80
                                      "\xC2\xB0"
5315
80
                                      "E") {
5316
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
5317
80
            } else if (orientation == "Geocentre > north pole") {
5318
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
5319
80
            } else if (starts_with(orientation, "North along ")) {
5320
48
                direction = &(cs::AxisDirection::NORTH);
5321
48
                meridian =
5322
48
                    createMeridian(orientation.substr(strlen("North along ")));
5323
48
            } else if (starts_with(orientation, "South along ")) {
5324
32
                direction = &(cs::AxisDirection::SOUTH);
5325
32
                meridian =
5326
32
                    createMeridian(orientation.substr(strlen("South along ")));
5327
32
            } else {
5328
0
                throw FactoryException("unknown axis direction: " +
5329
0
                                       orientation);
5330
0
            }
5331
80
        }
5332
39.8k
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5333
39.8k
            props, abbrev, *direction, uom, meridian));
5334
39.8k
    }
5335
5336
16.2k
    const auto cacheAndRet = [this,
5337
16.2k
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5338
16.2k
        d->context()->d->cache(cacheKey, cs);
5339
16.2k
        return cs;
5340
16.2k
    };
5341
5342
16.2k
    auto props = util::PropertyMap()
5343
16.2k
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5344
16.2k
                     .set(metadata::Identifier::CODE_KEY, code);
5345
16.2k
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5346
11.4k
        if (axisList.size() == 2) {
5347
6.30k
            return cacheAndRet(
5348
6.30k
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5349
6.30k
        }
5350
5.16k
        if (axisList.size() == 3) {
5351
5.16k
            return cacheAndRet(cs::EllipsoidalCS::create(
5352
5.16k
                props, axisList[0], axisList[1], axisList[2]));
5353
5.16k
        }
5354
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5355
5.16k
    }
5356
4.74k
    if (csType == CS_TYPE_CARTESIAN) {
5357
4.16k
        if (axisList.size() == 2) {
5358
1.30k
            return cacheAndRet(
5359
1.30k
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5360
1.30k
        }
5361
2.86k
        if (axisList.size() == 3) {
5362
2.86k
            return cacheAndRet(cs::CartesianCS::create(
5363
2.86k
                props, axisList[0], axisList[1], axisList[2]));
5364
2.86k
        }
5365
0
        throw FactoryException("invalid number of axis for CartesianCS");
5366
2.86k
    }
5367
578
    if (csType == CS_TYPE_SPHERICAL) {
5368
13
        if (axisList.size() == 2) {
5369
13
            return cacheAndRet(
5370
13
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5371
13
        }
5372
0
        if (axisList.size() == 3) {
5373
0
            return cacheAndRet(cs::SphericalCS::create(
5374
0
                props, axisList[0], axisList[1], axisList[2]));
5375
0
        }
5376
0
        throw FactoryException("invalid number of axis for SphericalCS");
5377
0
    }
5378
565
    if (csType == CS_TYPE_VERTICAL) {
5379
565
        if (axisList.size() == 1) {
5380
565
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5381
565
        }
5382
0
        throw FactoryException("invalid number of axis for VerticalCS");
5383
565
    }
5384
0
    if (csType == CS_TYPE_ORDINAL) {
5385
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5386
0
    }
5387
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5388
0
}
5389
5390
// ---------------------------------------------------------------------------
5391
5392
/** \brief Returns a crs::GeodeticCRS from the specified code.
5393
 *
5394
 * @param code Object code allocated by authority.
5395
 * @return object.
5396
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5397
 * @throw FactoryException in case of other errors.
5398
 */
5399
5400
crs::GeodeticCRSNNPtr
5401
133k
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5402
133k
    return createGeodeticCRS(code, false);
5403
133k
}
5404
5405
// ---------------------------------------------------------------------------
5406
5407
/** \brief Returns a crs::GeographicCRS from the specified code.
5408
 *
5409
 * @param code Object code allocated by authority.
5410
 * @return object.
5411
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5412
 * @throw FactoryException in case of other errors.
5413
 */
5414
5415
crs::GeographicCRSNNPtr
5416
761
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5417
761
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5418
761
        createGeodeticCRS(code, true)));
5419
761
    if (!crs) {
5420
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5421
0
                                           d->authority(), code);
5422
0
    }
5423
761
    return NN_NO_CHECK(crs);
5424
761
}
5425
5426
// ---------------------------------------------------------------------------
5427
5428
static crs::GeodeticCRSNNPtr
5429
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5430
0
               const util::PropertyMap &props) {
5431
0
    auto cs = geodCRS->coordinateSystem();
5432
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5433
0
    if (ellipsoidalCS) {
5434
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5435
0
                                          geodCRS->datumEnsemble(),
5436
0
                                          NN_NO_CHECK(ellipsoidalCS));
5437
0
    }
5438
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5439
0
    if (geocentricCS) {
5440
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5441
0
                                        geodCRS->datumEnsemble(),
5442
0
                                        NN_NO_CHECK(geocentricCS));
5443
0
    }
5444
0
    return geodCRS;
5445
0
}
5446
5447
// ---------------------------------------------------------------------------
5448
5449
crs::GeodeticCRSNNPtr
5450
AuthorityFactory::createGeodeticCRS(const std::string &code,
5451
134k
                                    bool geographicOnly) const {
5452
134k
    const auto cacheKey(d->authority() + code);
5453
134k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5454
134k
    if (crs) {
5455
92.4k
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5456
92.4k
        if (geogCRS) {
5457
92.4k
            return NN_NO_CHECK(geogCRS);
5458
92.4k
        }
5459
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5460
0
                                           d->authority(), code);
5461
92.4k
    }
5462
42.1k
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5463
42.1k
                    "coordinate_system_code, datum_auth_name, datum_code, "
5464
42.1k
                    "text_definition, deprecated, description FROM "
5465
42.1k
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5466
42.1k
    if (geographicOnly) {
5467
27
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5468
27
               ")";
5469
27
    }
5470
42.1k
    auto res = d->runWithCodeParam(sql, code);
5471
42.1k
    if (res.empty()) {
5472
27
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5473
27
                                           d->authority(), code);
5474
27
    }
5475
42.1k
    try {
5476
42.1k
        const auto &row = res.front();
5477
42.1k
        const auto &name = row[0];
5478
42.1k
        const auto &type = row[1];
5479
42.1k
        const auto &cs_auth_name = row[2];
5480
42.1k
        const auto &cs_code = row[3];
5481
42.1k
        const auto &datum_auth_name = row[4];
5482
42.1k
        const auto &datum_code = row[5];
5483
42.1k
        const auto &text_definition = row[6];
5484
42.1k
        const bool deprecated = row[7] == "1";
5485
42.1k
        const auto &remarks = row[8];
5486
5487
42.1k
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5488
42.1k
                                                     deprecated, remarks);
5489
5490
42.1k
        if (!text_definition.empty()) {
5491
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5492
0
            auto obj = createFromUserInput(
5493
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5494
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5495
0
            if (geodCRS) {
5496
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5497
0
                d->context()->d->cache(cacheKey, crsRet);
5498
0
                return crsRet;
5499
0
            }
5500
5501
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5502
0
            if (boundCRS) {
5503
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5504
0
                    boundCRS->baseCRS());
5505
0
                if (geodCRS) {
5506
0
                    auto newBoundCRS = crs::BoundCRS::create(
5507
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5508
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5509
0
                    return NN_NO_CHECK(
5510
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5511
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5512
0
                }
5513
0
            }
5514
5515
0
            throw FactoryException(
5516
0
                "text_definition does not define a GeodeticCRS");
5517
0
        }
5518
5519
42.1k
        auto cs =
5520
42.1k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5521
42.1k
        datum::GeodeticReferenceFramePtr datum;
5522
42.1k
        datum::DatumEnsemblePtr datumEnsemble;
5523
42.1k
        constexpr bool turnEnsembleAsDatum = false;
5524
42.1k
        d->createFactory(datum_auth_name)
5525
42.1k
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5526
42.1k
                                            turnEnsembleAsDatum);
5527
5528
42.1k
        auto ellipsoidalCS =
5529
42.1k
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5530
42.1k
        if ((type == CRS_SUBTYPE_GEOG_2D || type == CRS_SUBTYPE_GEOG_3D) &&
5531
31.3k
            ellipsoidalCS) {
5532
31.3k
            auto crsRet = crs::GeographicCRS::create(
5533
31.3k
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5534
31.3k
            d->context()->d->cache(cacheKey, crsRet);
5535
31.3k
            return crsRet;
5536
31.3k
        }
5537
5538
10.7k
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5539
10.7k
        if (type == CRS_SUBTYPE_GEOCENTRIC && geocentricCS) {
5540
10.7k
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5541
10.7k
                                                   NN_NO_CHECK(geocentricCS));
5542
10.7k
            d->context()->d->cache(cacheKey, crsRet);
5543
10.7k
            return crsRet;
5544
10.7k
        }
5545
5546
26
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5547
26
        if (type == CRS_SUBTYPE_OTHER && sphericalCS) {
5548
26
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5549
26
                                                   NN_NO_CHECK(sphericalCS));
5550
26
            d->context()->d->cache(cacheKey, crsRet);
5551
26
            return crsRet;
5552
26
        }
5553
5554
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5555
0
                               type + ", " + cs->getWKT2Type(true));
5556
26
    } catch (const std::exception &ex) {
5557
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5558
0
    }
5559
42.1k
}
5560
5561
// ---------------------------------------------------------------------------
5562
5563
/** \brief Returns a crs::VerticalCRS from the specified code.
5564
 *
5565
 * @param code Object code allocated by authority.
5566
 * @return object.
5567
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5568
 * @throw FactoryException in case of other errors.
5569
 */
5570
5571
crs::VerticalCRSNNPtr
5572
1.28k
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5573
1.28k
    const auto cacheKey(d->authority() + code);
5574
1.28k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5575
1.28k
    if (crs) {
5576
197
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5577
197
        if (projCRS) {
5578
197
            return NN_NO_CHECK(projCRS);
5579
197
        }
5580
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5581
0
                                           d->authority(), code);
5582
197
    }
5583
1.08k
    auto res = d->runWithCodeParam(
5584
1.08k
        "SELECT name, coordinate_system_auth_name, "
5585
1.08k
        "coordinate_system_code, datum_auth_name, datum_code, "
5586
1.08k
        "deprecated FROM "
5587
1.08k
        "vertical_crs WHERE auth_name = ? AND code = ?",
5588
1.08k
        code);
5589
1.08k
    if (res.empty()) {
5590
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5591
0
                                           d->authority(), code);
5592
0
    }
5593
1.08k
    try {
5594
1.08k
        const auto &row = res.front();
5595
1.08k
        const auto &name = row[0];
5596
1.08k
        const auto &cs_auth_name = row[1];
5597
1.08k
        const auto &cs_code = row[2];
5598
1.08k
        const auto &datum_auth_name = row[3];
5599
1.08k
        const auto &datum_code = row[4];
5600
1.08k
        const bool deprecated = row[5] == "1";
5601
1.08k
        auto cs =
5602
1.08k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5603
1.08k
        datum::VerticalReferenceFramePtr datum;
5604
1.08k
        datum::DatumEnsemblePtr datumEnsemble;
5605
1.08k
        constexpr bool turnEnsembleAsDatum = false;
5606
1.08k
        d->createFactory(datum_auth_name)
5607
1.08k
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5608
1.08k
                                            turnEnsembleAsDatum);
5609
1.08k
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5610
1.08k
                                                     deprecated);
5611
5612
1.08k
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5613
1.08k
        if (verticalCS) {
5614
1.08k
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5615
1.08k
                                                   NN_NO_CHECK(verticalCS));
5616
1.08k
            d->context()->d->cache(cacheKey, crsRet);
5617
1.08k
            return crsRet;
5618
1.08k
        }
5619
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5620
0
                               cs->getWKT2Type(true));
5621
1.08k
    } catch (const std::exception &ex) {
5622
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5623
0
    }
5624
1.08k
}
5625
5626
// ---------------------------------------------------------------------------
5627
5628
/** \brief Returns a crs::EngineeringCRS from the specified code.
5629
 *
5630
 * @param code Object code allocated by authority.
5631
 * @return object.
5632
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5633
 * @throw FactoryException in case of other errors.
5634
 * @since 9.6
5635
 */
5636
5637
crs::EngineeringCRSNNPtr
5638
20
AuthorityFactory::createEngineeringCRS(const std::string &code) const {
5639
20
    const auto cacheKey(d->authority() + code);
5640
20
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5641
20
    if (crs) {
5642
0
        auto engCRS = std::dynamic_pointer_cast<crs::EngineeringCRS>(crs);
5643
0
        if (engCRS) {
5644
0
            return NN_NO_CHECK(engCRS);
5645
0
        }
5646
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5647
0
                                           d->authority(), code);
5648
0
    }
5649
20
    auto res = d->runWithCodeParam(
5650
20
        "SELECT name, coordinate_system_auth_name, "
5651
20
        "coordinate_system_code, datum_auth_name, datum_code, "
5652
20
        "deprecated FROM "
5653
20
        "engineering_crs WHERE auth_name = ? AND code = ?",
5654
20
        code);
5655
20
    if (res.empty()) {
5656
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5657
0
                                           d->authority(), code);
5658
0
    }
5659
20
    try {
5660
20
        const auto &row = res.front();
5661
20
        const auto &name = row[0];
5662
20
        const auto &cs_auth_name = row[1];
5663
20
        const auto &cs_code = row[2];
5664
20
        const auto &datum_auth_name = row[3];
5665
20
        const auto &datum_code = row[4];
5666
20
        const bool deprecated = row[5] == "1";
5667
20
        auto cs =
5668
20
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5669
20
        auto datum = d->createFactory(datum_auth_name)
5670
20
                         ->createEngineeringDatum(datum_code);
5671
20
        auto props = d->createPropertiesSearchUsages("engineering_crs", code,
5672
20
                                                     name, deprecated);
5673
20
        auto crsRet = crs::EngineeringCRS::create(props, datum, cs);
5674
20
        d->context()->d->cache(cacheKey, crsRet);
5675
20
        return crsRet;
5676
20
    } catch (const std::exception &ex) {
5677
0
        throw buildFactoryException("engineeringCRS", d->authority(), code, ex);
5678
0
    }
5679
20
}
5680
5681
// ---------------------------------------------------------------------------
5682
5683
/** \brief Returns a operation::Conversion from the specified code.
5684
 *
5685
 * @param code Object code allocated by authority.
5686
 * @return object.
5687
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5688
 * @throw FactoryException in case of other errors.
5689
 */
5690
5691
operation::ConversionNNPtr
5692
3.67k
AuthorityFactory::createConversion(const std::string &code) const {
5693
5694
3.67k
    static const char *sql =
5695
3.67k
        "SELECT name, description, "
5696
3.67k
        "method_auth_name, method_code, method_name, "
5697
5698
3.67k
        "param1_auth_name, param1_code, param1_name, param1_value, "
5699
3.67k
        "param1_uom_auth_name, param1_uom_code, "
5700
5701
3.67k
        "param2_auth_name, param2_code, param2_name, param2_value, "
5702
3.67k
        "param2_uom_auth_name, param2_uom_code, "
5703
5704
3.67k
        "param3_auth_name, param3_code, param3_name, param3_value, "
5705
3.67k
        "param3_uom_auth_name, param3_uom_code, "
5706
5707
3.67k
        "param4_auth_name, param4_code, param4_name, param4_value, "
5708
3.67k
        "param4_uom_auth_name, param4_uom_code, "
5709
5710
3.67k
        "param5_auth_name, param5_code, param5_name, param5_value, "
5711
3.67k
        "param5_uom_auth_name, param5_uom_code, "
5712
5713
3.67k
        "param6_auth_name, param6_code, param6_name, param6_value, "
5714
3.67k
        "param6_uom_auth_name, param6_uom_code, "
5715
5716
3.67k
        "param7_auth_name, param7_code, param7_name, param7_value, "
5717
3.67k
        "param7_uom_auth_name, param7_uom_code, "
5718
5719
3.67k
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5720
5721
3.67k
    auto res = d->runWithCodeParam(sql, code);
5722
3.67k
    if (res.empty()) {
5723
0
        try {
5724
            // Conversions using methods Change of Vertical Unit or
5725
            // Height Depth Reversal are stored in other_transformation
5726
0
            auto op = createCoordinateOperation(
5727
0
                code, false /* allowConcatenated */,
5728
0
                false /* usePROJAlternativeGridNames */,
5729
0
                "other_transformation");
5730
0
            auto conv =
5731
0
                util::nn_dynamic_pointer_cast<operation::Conversion>(op);
5732
0
            if (conv) {
5733
0
                return NN_NO_CHECK(conv);
5734
0
            }
5735
0
        } catch (const std::exception &) {
5736
0
        }
5737
0
        throw NoSuchAuthorityCodeException("conversion not found",
5738
0
                                           d->authority(), code);
5739
0
    }
5740
3.67k
    try {
5741
3.67k
        const auto &row = res.front();
5742
3.67k
        size_t idx = 0;
5743
3.67k
        const auto &name = row[idx++];
5744
3.67k
        const auto &description = row[idx++];
5745
3.67k
        const auto &method_auth_name = row[idx++];
5746
3.67k
        const auto &method_code = row[idx++];
5747
3.67k
        const auto &method_name = row[idx++];
5748
3.67k
        const size_t base_param_idx = idx;
5749
3.67k
        std::vector<operation::OperationParameterNNPtr> parameters;
5750
3.67k
        std::vector<operation::ParameterValueNNPtr> values;
5751
22.1k
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5752
22.0k
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5753
22.0k
            if (param_auth_name.empty()) {
5754
3.61k
                break;
5755
3.61k
            }
5756
18.4k
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5757
18.4k
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5758
18.4k
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5759
18.4k
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5760
18.4k
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5761
18.4k
            parameters.emplace_back(operation::OperationParameter::create(
5762
18.4k
                util::PropertyMap()
5763
18.4k
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5764
18.4k
                    .set(metadata::Identifier::CODE_KEY, param_code)
5765
18.4k
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5766
18.4k
            std::string normalized_uom_code(param_uom_code);
5767
18.4k
            const double normalized_value = normalizeMeasure(
5768
18.4k
                param_uom_code, param_value, normalized_uom_code);
5769
18.4k
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5770
18.4k
                                              normalized_uom_code);
5771
18.4k
            values.emplace_back(operation::ParameterValue::create(
5772
18.4k
                common::Measure(normalized_value, uom)));
5773
18.4k
        }
5774
3.67k
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5775
5776
3.67k
        auto propConversion = d->createPropertiesSearchUsages(
5777
3.67k
            "conversion", code, name, deprecated);
5778
3.67k
        if (!description.empty())
5779
1.41k
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5780
1.41k
                               description);
5781
5782
3.67k
        auto propMethod = util::PropertyMap().set(
5783
3.67k
            common::IdentifiedObject::NAME_KEY, method_name);
5784
3.67k
        if (!method_auth_name.empty()) {
5785
3.67k
            propMethod
5786
3.67k
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5787
3.67k
                .set(metadata::Identifier::CODE_KEY, method_code);
5788
3.67k
        }
5789
5790
3.67k
        return operation::Conversion::create(propConversion, propMethod,
5791
3.67k
                                             parameters, values);
5792
3.67k
    } catch (const std::exception &ex) {
5793
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5794
0
    }
5795
3.67k
}
5796
5797
// ---------------------------------------------------------------------------
5798
5799
/** \brief Returns a crs::ProjectedCRS from the specified code.
5800
 *
5801
 * @param code Object code allocated by authority.
5802
 * @return object.
5803
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5804
 * @throw FactoryException in case of other errors.
5805
 */
5806
5807
crs::ProjectedCRSNNPtr
5808
3.53k
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5809
3.53k
    const auto cacheKey(d->authority() + code);
5810
3.53k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5811
3.53k
    if (crs) {
5812
49
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5813
49
        if (projCRS) {
5814
49
            return NN_NO_CHECK(projCRS);
5815
49
        }
5816
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5817
0
                                           d->authority(), code);
5818
49
    }
5819
3.49k
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5820
3.53k
}
5821
5822
// ---------------------------------------------------------------------------
5823
//! @cond Doxygen_Suppress
5824
5825
/** Returns the result of the SQL query needed by createProjectedCRSEnd
5826
 *
5827
 * The split in two functions is for createFromCoordinateReferenceSystemCodes()
5828
 * convenience, to avoid throwing exceptions.
5829
 */
5830
SQLResultSet
5831
3.49k
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5832
3.49k
    return runWithCodeParam(
5833
3.49k
        "SELECT name, coordinate_system_auth_name, "
5834
3.49k
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5835
3.49k
        "conversion_auth_name, conversion_code, "
5836
3.49k
        "text_definition, "
5837
3.49k
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5838
3.49k
        code);
5839
3.49k
}
5840
5841
// ---------------------------------------------------------------------------
5842
5843
/** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
5844
crs::ProjectedCRSNNPtr
5845
AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
5846
3.49k
                                                 const SQLResultSet &res) {
5847
3.49k
    const auto cacheKey(authority() + code);
5848
3.49k
    if (res.empty()) {
5849
7
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5850
7
                                           authority(), code);
5851
7
    }
5852
3.48k
    try {
5853
3.48k
        const auto &row = res.front();
5854
3.48k
        const auto &name = row[0];
5855
3.48k
        const auto &cs_auth_name = row[1];
5856
3.48k
        const auto &cs_code = row[2];
5857
3.48k
        const auto &geodetic_crs_auth_name = row[3];
5858
3.48k
        const auto &geodetic_crs_code = row[4];
5859
3.48k
        const auto &conversion_auth_name = row[5];
5860
3.48k
        const auto &conversion_code = row[6];
5861
3.48k
        const auto &text_definition = row[7];
5862
3.48k
        const bool deprecated = row[8] == "1";
5863
5864
3.48k
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5865
3.48k
                                                  deprecated);
5866
5867
3.48k
        if (!text_definition.empty()) {
5868
318
            DatabaseContext::Private::RecursionDetector detector(context());
5869
318
            auto obj = createFromUserInput(
5870
318
                pj_add_type_crs_if_needed(text_definition), context());
5871
318
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5872
318
            if (projCRS) {
5873
318
                auto conv = projCRS->derivingConversion();
5874
318
                auto newConv =
5875
318
                    (conv->nameStr() == "unnamed")
5876
318
                        ? operation::Conversion::create(
5877
305
                              util::PropertyMap().set(
5878
305
                                  common::IdentifiedObject::NAME_KEY, name),
5879
305
                              conv->method(), conv->parameterValues())
5880
318
                        : std::move(conv);
5881
318
                auto crsRet = crs::ProjectedCRS::create(
5882
318
                    props, projCRS->baseCRS(), newConv,
5883
318
                    projCRS->coordinateSystem());
5884
318
                context()->d->cache(cacheKey, crsRet);
5885
318
                return crsRet;
5886
318
            }
5887
5888
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5889
0
            if (boundCRS) {
5890
0
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
5891
0
                    boundCRS->baseCRS().get());
5892
0
                if (projCRS) {
5893
0
                    auto newBoundCRS = crs::BoundCRS::create(
5894
0
                        crs::ProjectedCRS::create(props, projCRS->baseCRS(),
5895
0
                                                  projCRS->derivingConversion(),
5896
0
                                                  projCRS->coordinateSystem()),
5897
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5898
0
                    return NN_NO_CHECK(
5899
0
                        util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
5900
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5901
0
                }
5902
0
            }
5903
5904
0
            throw FactoryException(
5905
0
                "text_definition does not define a ProjectedCRS");
5906
0
        }
5907
5908
3.16k
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5909
5910
3.16k
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5911
3.16k
                           ->createGeodeticCRS(geodetic_crs_code);
5912
5913
3.16k
        auto conv = createFactory(conversion_auth_name)
5914
3.16k
                        ->createConversion(conversion_code);
5915
3.16k
        if (conv->nameStr() == "unnamed") {
5916
479
            conv = conv->shallowClone();
5917
479
            conv->setProperties(util::PropertyMap().set(
5918
479
                common::IdentifiedObject::NAME_KEY, name));
5919
479
        }
5920
5921
3.16k
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5922
3.16k
        if (cartesianCS) {
5923
3.16k
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5924
3.16k
                                                    NN_NO_CHECK(cartesianCS));
5925
3.16k
            context()->d->cache(cacheKey, crsRet);
5926
3.16k
            return crsRet;
5927
3.16k
        }
5928
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5929
0
                               cs->getWKT2Type(true));
5930
3.16k
    } catch (const std::exception &ex) {
5931
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5932
0
    }
5933
3.48k
}
5934
//! @endcond
5935
5936
// ---------------------------------------------------------------------------
5937
5938
/** \brief Returns a crs::DerivedProjectedCRS from the specified code.
5939
 *
5940
 * @param code Object code allocated by authority.
5941
 * @return object.
5942
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5943
 * @throw FactoryException in case of other errors.
5944
 */
5945
crs::DerivedProjectedCRSNNPtr
5946
0
AuthorityFactory::createDerivedProjectedCRS(const std::string &code) const {
5947
0
    const auto cacheKey(d->authority() + code);
5948
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5949
0
    if (crs) {
5950
0
        auto derivedProjCRS =
5951
0
            std::dynamic_pointer_cast<crs::DerivedProjectedCRS>(crs);
5952
0
        if (derivedProjCRS) {
5953
0
            return NN_NO_CHECK(derivedProjCRS);
5954
0
        }
5955
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5956
0
                                           d->authority(), code);
5957
0
    }
5958
0
    return d->createDerivedProjectedCRSEnd(
5959
0
        code, d->createDerivedProjectedCRSBegin(code));
5960
0
}
5961
5962
// ---------------------------------------------------------------------------
5963
//! @cond Doxygen_Suppress
5964
5965
/** Returns the result of the SQL query needed by createDerivedProjectedCRSEnd
5966
 */
5967
SQLResultSet AuthorityFactory::Private::createDerivedProjectedCRSBegin(
5968
0
    const std::string &code) {
5969
0
    return runWithCodeParam(
5970
0
        "SELECT name, coordinate_system_auth_name, "
5971
0
        "coordinate_system_code, base_crs_auth_name, base_crs_code, "
5972
0
        "conversion_auth_name, conversion_code, "
5973
0
        "text_definition, "
5974
0
        "deprecated FROM derived_projected_crs WHERE auth_name = ? AND code = "
5975
0
        "?",
5976
0
        code);
5977
0
}
5978
5979
// ---------------------------------------------------------------------------
5980
5981
/** Build a DerivedProjectedCRS from the result of
5982
 * createDerivedProjectedCRSBegin() */
5983
crs::DerivedProjectedCRSNNPtr
5984
AuthorityFactory::Private::createDerivedProjectedCRSEnd(
5985
0
    const std::string &code, const SQLResultSet &res) {
5986
0
    const auto cacheKey(authority() + code);
5987
0
    if (res.empty()) {
5988
0
        throw NoSuchAuthorityCodeException("derivedProjectedCRS not found",
5989
0
                                           authority(), code);
5990
0
    }
5991
0
    try {
5992
0
        const auto &row = res.front();
5993
0
        const auto &name = row[0];
5994
0
        const auto &cs_auth_name = row[1];
5995
0
        const auto &cs_code = row[2];
5996
0
        const auto &base_crs_auth_name = row[3];
5997
0
        const auto &base_crs_code = row[4];
5998
0
        const auto &conversion_auth_name = row[5];
5999
0
        const auto &conversion_code = row[6];
6000
0
        const auto &text_definition = row[7];
6001
0
        const bool deprecated = row[8] == "1";
6002
6003
0
        auto props = createPropertiesSearchUsages("derived_projected_crs", code,
6004
0
                                                  name, deprecated);
6005
6006
0
        if (!text_definition.empty()) {
6007
0
            DatabaseContext::Private::RecursionDetector detector(context());
6008
0
            auto obj = createFromUserInput(
6009
0
                pj_add_type_crs_if_needed(text_definition), context());
6010
0
            auto derivedProjCRS =
6011
0
                dynamic_cast<const crs::DerivedProjectedCRS *>(obj.get());
6012
0
            if (derivedProjCRS) {
6013
0
                auto conv = derivedProjCRS->derivingConversion();
6014
0
                auto newConv =
6015
0
                    (conv->nameStr() == "unnamed")
6016
0
                        ? operation::Conversion::create(
6017
0
                              util::PropertyMap().set(
6018
0
                                  common::IdentifiedObject::NAME_KEY, name),
6019
0
                              conv->method(), conv->parameterValues())
6020
0
                        : std::move(conv);
6021
0
                auto crsRet = crs::DerivedProjectedCRS::create(
6022
0
                    props, derivedProjCRS->baseCRS(), newConv,
6023
0
                    derivedProjCRS->coordinateSystem());
6024
0
                context()->d->cache(cacheKey, crsRet);
6025
0
                return crsRet;
6026
0
            }
6027
0
            throw FactoryException(
6028
0
                "text_definition does not define a DerivedProjectedCRS");
6029
0
        }
6030
6031
0
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
6032
6033
0
        auto baseCRS = createFactory(base_crs_auth_name)
6034
0
                           ->createProjectedCRS(base_crs_code);
6035
6036
0
        auto conv = createFactory(conversion_auth_name)
6037
0
                        ->createConversion(conversion_code);
6038
0
        if (conv->nameStr() == "unnamed") {
6039
0
            conv = conv->shallowClone();
6040
0
            conv->setProperties(util::PropertyMap().set(
6041
0
                common::IdentifiedObject::NAME_KEY, name));
6042
0
        }
6043
6044
0
        auto crsRet =
6045
0
            crs::DerivedProjectedCRS::create(props, baseCRS, conv, cs);
6046
0
        context()->d->cache(cacheKey, crsRet);
6047
0
        return crsRet;
6048
0
    } catch (const std::exception &ex) {
6049
0
        throw buildFactoryException("derivedProjectedCRS", authority(), code,
6050
0
                                    ex);
6051
0
    }
6052
0
}
6053
//! @endcond
6054
6055
// ---------------------------------------------------------------------------
6056
6057
/** \brief Returns a crs::CompoundCRS from the specified code.
6058
 *
6059
 * @param code Object code allocated by authority.
6060
 * @return object.
6061
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6062
 * @throw FactoryException in case of other errors.
6063
 */
6064
6065
crs::CompoundCRSNNPtr
6066
159
AuthorityFactory::createCompoundCRS(const std::string &code) const {
6067
159
    auto res =
6068
159
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
6069
159
                            "vertical_crs_auth_name, vertical_crs_code, "
6070
159
                            "deprecated FROM "
6071
159
                            "compound_crs WHERE auth_name = ? AND code = ?",
6072
159
                            code);
6073
159
    if (res.empty()) {
6074
1
        throw NoSuchAuthorityCodeException("compoundCRS not found",
6075
1
                                           d->authority(), code);
6076
1
    }
6077
158
    try {
6078
158
        const auto &row = res.front();
6079
158
        const auto &name = row[0];
6080
158
        const auto &horiz_crs_auth_name = row[1];
6081
158
        const auto &horiz_crs_code = row[2];
6082
158
        const auto &vertical_crs_auth_name = row[3];
6083
158
        const auto &vertical_crs_code = row[4];
6084
158
        const bool deprecated = row[5] == "1";
6085
6086
158
        auto horizCRS =
6087
158
            d->createFactory(horiz_crs_auth_name)
6088
158
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
6089
158
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
6090
158
                           ->createVerticalCRS(vertical_crs_code);
6091
6092
158
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
6093
158
                                                     deprecated);
6094
158
        return crs::CompoundCRS::create(
6095
158
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
6096
158
                                              std::move(vertCRS)});
6097
158
    } catch (const std::exception &ex) {
6098
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
6099
0
    }
6100
158
}
6101
6102
// ---------------------------------------------------------------------------
6103
6104
/** \brief Returns a crs::CRS from the specified code.
6105
 *
6106
 * @param code Object code allocated by authority.
6107
 * @return object.
6108
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6109
 * @throw FactoryException in case of other errors.
6110
 */
6111
6112
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
6113
581k
    const std::string &code) const {
6114
581k
    return createCoordinateReferenceSystem(code, true);
6115
581k
}
6116
6117
//! @cond Doxygen_Suppress
6118
6119
crs::CRSNNPtr
6120
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
6121
582k
                                                  bool allowCompound) const {
6122
582k
    const auto cacheKey(d->authority() + code);
6123
582k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
6124
582k
    if (crs) {
6125
575k
        return NN_NO_CHECK(crs);
6126
575k
    }
6127
6128
6.91k
    if (d->authority() == metadata::Identifier::OGC) {
6129
23
        if (code == "AnsiDate") {
6130
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
6131
0
            return crs::TemporalCRS::create(
6132
0
                util::PropertyMap()
6133
                    // above URL indicates Julian Date" as name... likely wrong
6134
0
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
6135
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6136
0
                    .set(metadata::Identifier::CODE_KEY, code),
6137
0
                datum::TemporalDatum::create(
6138
0
                    util::PropertyMap().set(
6139
0
                        common::IdentifiedObject::NAME_KEY,
6140
0
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
6141
0
                        "as day 1."),
6142
0
                    common::DateTime::create("1600-12-31T00:00:00Z"),
6143
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6144
0
                cs::TemporalCountCS::create(
6145
0
                    util::PropertyMap(),
6146
0
                    cs::CoordinateSystemAxis::create(
6147
0
                        util::PropertyMap().set(
6148
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6149
0
                        "T", cs::AxisDirection::FUTURE,
6150
0
                        common::UnitOfMeasure("day", 0,
6151
0
                                              UnitOfMeasure::Type::TIME))));
6152
0
        }
6153
23
        if (code == "JulianDate") {
6154
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
6155
0
            return crs::TemporalCRS::create(
6156
0
                util::PropertyMap()
6157
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
6158
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6159
0
                    .set(metadata::Identifier::CODE_KEY, code),
6160
0
                datum::TemporalDatum::create(
6161
0
                    util::PropertyMap().set(
6162
0
                        common::IdentifiedObject::NAME_KEY,
6163
0
                        "The beginning of the Julian period."),
6164
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
6165
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6166
0
                cs::TemporalCountCS::create(
6167
0
                    util::PropertyMap(),
6168
0
                    cs::CoordinateSystemAxis::create(
6169
0
                        util::PropertyMap().set(
6170
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6171
0
                        "T", cs::AxisDirection::FUTURE,
6172
0
                        common::UnitOfMeasure("day", 0,
6173
0
                                              UnitOfMeasure::Type::TIME))));
6174
0
        }
6175
23
        if (code == "UnixTime") {
6176
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
6177
0
            return crs::TemporalCRS::create(
6178
0
                util::PropertyMap()
6179
0
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
6180
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6181
0
                    .set(metadata::Identifier::CODE_KEY, code),
6182
0
                datum::TemporalDatum::create(
6183
0
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6184
0
                                            "Unix epoch"),
6185
0
                    common::DateTime::create("1970-01-01T00:00:00Z"),
6186
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6187
0
                cs::TemporalCountCS::create(
6188
0
                    util::PropertyMap(),
6189
0
                    cs::CoordinateSystemAxis::create(
6190
0
                        util::PropertyMap().set(
6191
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6192
0
                        "T", cs::AxisDirection::FUTURE,
6193
0
                        common::UnitOfMeasure::SECOND)));
6194
0
        }
6195
23
        if (code == "84") {
6196
0
            return createCoordinateReferenceSystem("CRS84", false);
6197
0
        }
6198
23
    }
6199
6200
6.91k
    auto res = d->runWithCodeParam(
6201
6.91k
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
6202
6.91k
    if (res.empty()) {
6203
548
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
6204
548
                                           code);
6205
548
    }
6206
6.36k
    const auto &type = res.front()[0];
6207
6.36k
    if (type == CRS_SUBTYPE_GEOG_2D || type == CRS_SUBTYPE_GEOG_3D ||
6208
6.09k
        type == CRS_SUBTYPE_GEOCENTRIC || type == CRS_SUBTYPE_OTHER) {
6209
6.09k
        return createGeodeticCRS(code);
6210
6.09k
    }
6211
267
    if (type == CRS_SUBTYPE_VERTICAL) {
6212
142
        return createVerticalCRS(code);
6213
142
    }
6214
125
    if (type == CRS_SUBTYPE_PROJECTED) {
6215
91
        return createProjectedCRS(code);
6216
91
    }
6217
34
    if (type == CRS_SUBTYPE_DERIVED_PROJECTED) {
6218
0
        return createDerivedProjectedCRS(code);
6219
0
    }
6220
34
    if (type == CRS_SUBTYPE_ENGINEERING) {
6221
3
        return createEngineeringCRS(code);
6222
3
    }
6223
31
    if (allowCompound && type == CRS_SUBTYPE_COMPOUND) {
6224
31
        return createCompoundCRS(code);
6225
31
    }
6226
0
    throw FactoryException("unhandled CRS type: " + type);
6227
31
}
6228
6229
//! @endcond
6230
6231
// ---------------------------------------------------------------------------
6232
6233
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
6234
 *
6235
 * @param code Object code allocated by authority.
6236
 * @return object.
6237
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6238
 * @throw FactoryException in case of other errors.
6239
 * @since 9.4
6240
 */
6241
6242
coordinates::CoordinateMetadataNNPtr
6243
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
6244
0
    auto res = d->runWithCodeParam(
6245
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
6246
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
6247
0
        code);
6248
0
    if (res.empty()) {
6249
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
6250
0
                                           d->authority(), code);
6251
0
    }
6252
0
    try {
6253
0
        const auto &row = res.front();
6254
0
        const auto &crs_auth_name = row[0];
6255
0
        const auto &crs_code = row[1];
6256
0
        const auto &crs_text_definition = row[2];
6257
0
        const auto &coordinate_epoch = row[3];
6258
6259
0
        auto l_context = d->context();
6260
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
6261
0
        auto crs =
6262
0
            !crs_auth_name.empty()
6263
0
                ? d->createFactory(crs_auth_name)
6264
0
                      ->createCoordinateReferenceSystem(crs_code)
6265
0
                      .as_nullable()
6266
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
6267
0
                      createFromUserInput(crs_text_definition, l_context));
6268
0
        if (!crs) {
6269
0
            throw FactoryException(
6270
0
                std::string("cannot build CoordinateMetadata ") +
6271
0
                d->authority() + ":" + code + ": cannot build CRS");
6272
0
        }
6273
0
        if (coordinate_epoch.empty()) {
6274
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
6275
0
        } else {
6276
0
            return coordinates::CoordinateMetadata::create(
6277
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
6278
0
                l_context.as_nullable());
6279
0
        }
6280
0
    } catch (const std::exception &ex) {
6281
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
6282
0
                                    ex);
6283
0
    }
6284
0
}
6285
6286
// ---------------------------------------------------------------------------
6287
6288
//! @cond Doxygen_Suppress
6289
6290
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
6291
121k
                                               int code) {
6292
121k
    return util::PropertyMap()
6293
121k
        .set(common::IdentifiedObject::NAME_KEY, name)
6294
121k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
6295
121k
        .set(metadata::Identifier::CODE_KEY, code);
6296
121k
}
6297
6298
// ---------------------------------------------------------------------------
6299
6300
121k
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
6301
121k
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
6302
121k
    assert(name);
6303
121k
    return operation::OperationParameter::create(
6304
121k
        createMapNameEPSGCode(name, code));
6305
121k
}
6306
6307
static operation::ParameterValueNNPtr createLength(const std::string &value,
6308
101k
                                                   const UnitOfMeasure &uom) {
6309
101k
    return operation::ParameterValue::create(
6310
101k
        common::Length(c_locale_stod(value), uom));
6311
101k
}
6312
6313
static operation::ParameterValueNNPtr createAngle(const std::string &value,
6314
14.2k
                                                  const UnitOfMeasure &uom) {
6315
14.2k
    return operation::ParameterValue::create(
6316
14.2k
        common::Angle(c_locale_stod(value), uom));
6317
14.2k
}
6318
6319
//! @endcond
6320
6321
// ---------------------------------------------------------------------------
6322
6323
/** \brief Returns a operation::CoordinateOperation from the specified code.
6324
 *
6325
 * @param code Object code allocated by authority.
6326
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6327
 * should be substituted to the official grid names.
6328
 * @return object.
6329
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6330
 * @throw FactoryException in case of other errors.
6331
 */
6332
6333
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6334
984
    const std::string &code, bool usePROJAlternativeGridNames) const {
6335
984
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
6336
984
                                     std::string());
6337
984
}
6338
6339
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6340
    const std::string &code, bool allowConcatenated,
6341
183k
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
6342
183k
    std::string type(typeIn);
6343
183k
    if (type.empty()) {
6344
79.1k
        auto res = d->runWithCodeParam(
6345
79.1k
            "SELECT type FROM coordinate_operation_with_conversion_view "
6346
79.1k
            "WHERE auth_name = ? AND code = ?",
6347
79.1k
            code);
6348
79.1k
        if (res.empty()) {
6349
1
            throw NoSuchAuthorityCodeException("coordinate operation not found",
6350
1
                                               d->authority(), code);
6351
1
        }
6352
79.1k
        type = res.front()[0];
6353
79.1k
    }
6354
6355
183k
    if (type == "conversion") {
6356
30
        return createConversion(code);
6357
30
    }
6358
6359
183k
    if (type == "helmert_transformation") {
6360
6361
32.8k
        auto res = d->runWithCodeParam(
6362
32.8k
            "SELECT name, description, "
6363
32.8k
            "method_auth_name, method_code, method_name, "
6364
32.8k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6365
32.8k
            "target_crs_code, "
6366
32.8k
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
6367
32.8k
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
6368
32.8k
            "rotation_uom_code, scale_difference, "
6369
32.8k
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
6370
32.8k
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
6371
32.8k
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
6372
32.8k
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
6373
32.8k
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
6374
32.8k
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
6375
32.8k
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
6376
32.8k
            "operation_version, deprecated FROM "
6377
32.8k
            "helmert_transformation WHERE auth_name = ? AND code = ?",
6378
32.8k
            code);
6379
32.8k
        if (res.empty()) {
6380
            // shouldn't happen if foreign keys are OK
6381
0
            throw NoSuchAuthorityCodeException(
6382
0
                "helmert_transformation not found", d->authority(), code);
6383
0
        }
6384
32.8k
        try {
6385
32.8k
            const auto &row = res.front();
6386
32.8k
            size_t idx = 0;
6387
32.8k
            const auto &name = row[idx++];
6388
32.8k
            const auto &description = row[idx++];
6389
32.8k
            const auto &method_auth_name = row[idx++];
6390
32.8k
            const auto &method_code = row[idx++];
6391
32.8k
            const auto &method_name = row[idx++];
6392
32.8k
            const auto &source_crs_auth_name = row[idx++];
6393
32.8k
            const auto &source_crs_code = row[idx++];
6394
32.8k
            const auto &target_crs_auth_name = row[idx++];
6395
32.8k
            const auto &target_crs_code = row[idx++];
6396
32.8k
            const auto &accuracy = row[idx++];
6397
6398
32.8k
            const auto &tx = row[idx++];
6399
32.8k
            const auto &ty = row[idx++];
6400
32.8k
            const auto &tz = row[idx++];
6401
32.8k
            const auto &translation_uom_auth_name = row[idx++];
6402
32.8k
            const auto &translation_uom_code = row[idx++];
6403
32.8k
            const auto &rx = row[idx++];
6404
32.8k
            const auto &ry = row[idx++];
6405
32.8k
            const auto &rz = row[idx++];
6406
32.8k
            const auto &rotation_uom_auth_name = row[idx++];
6407
32.8k
            const auto &rotation_uom_code = row[idx++];
6408
32.8k
            const auto &scale_difference = row[idx++];
6409
32.8k
            const auto &scale_difference_uom_auth_name = row[idx++];
6410
32.8k
            const auto &scale_difference_uom_code = row[idx++];
6411
6412
32.8k
            const auto &rate_tx = row[idx++];
6413
32.8k
            const auto &rate_ty = row[idx++];
6414
32.8k
            const auto &rate_tz = row[idx++];
6415
32.8k
            const auto &rate_translation_uom_auth_name = row[idx++];
6416
32.8k
            const auto &rate_translation_uom_code = row[idx++];
6417
32.8k
            const auto &rate_rx = row[idx++];
6418
32.8k
            const auto &rate_ry = row[idx++];
6419
32.8k
            const auto &rate_rz = row[idx++];
6420
32.8k
            const auto &rate_rotation_uom_auth_name = row[idx++];
6421
32.8k
            const auto &rate_rotation_uom_code = row[idx++];
6422
32.8k
            const auto &rate_scale_difference = row[idx++];
6423
32.8k
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
6424
32.8k
            const auto &rate_scale_difference_uom_code = row[idx++];
6425
6426
32.8k
            const auto &epoch = row[idx++];
6427
32.8k
            const auto &epoch_uom_auth_name = row[idx++];
6428
32.8k
            const auto &epoch_uom_code = row[idx++];
6429
6430
32.8k
            const auto &px = row[idx++];
6431
32.8k
            const auto &py = row[idx++];
6432
32.8k
            const auto &pz = row[idx++];
6433
32.8k
            const auto &pivot_uom_auth_name = row[idx++];
6434
32.8k
            const auto &pivot_uom_code = row[idx++];
6435
6436
32.8k
            const auto &operation_version = row[idx++];
6437
32.8k
            const auto &deprecated_str = row[idx++];
6438
32.8k
            const bool deprecated = deprecated_str == "1";
6439
32.8k
            assert(idx == row.size());
6440
6441
32.8k
            auto uom_translation = d->createUnitOfMeasure(
6442
32.8k
                translation_uom_auth_name, translation_uom_code);
6443
6444
32.8k
            auto uom_epoch = epoch_uom_auth_name.empty()
6445
32.8k
                                 ? common::UnitOfMeasure::NONE
6446
32.8k
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
6447
1.01k
                                                          epoch_uom_code);
6448
6449
32.8k
            auto sourceCRS =
6450
32.8k
                d->createFactory(source_crs_auth_name)
6451
32.8k
                    ->createCoordinateReferenceSystem(source_crs_code);
6452
32.8k
            auto targetCRS =
6453
32.8k
                d->createFactory(target_crs_auth_name)
6454
32.8k
                    ->createCoordinateReferenceSystem(target_crs_code);
6455
6456
32.8k
            std::vector<operation::OperationParameterNNPtr> parameters;
6457
32.8k
            std::vector<operation::ParameterValueNNPtr> values;
6458
6459
32.8k
            parameters.emplace_back(createOpParamNameEPSGCode(
6460
32.8k
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
6461
32.8k
            values.emplace_back(createLength(tx, uom_translation));
6462
6463
32.8k
            parameters.emplace_back(createOpParamNameEPSGCode(
6464
32.8k
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
6465
32.8k
            values.emplace_back(createLength(ty, uom_translation));
6466
6467
32.8k
            parameters.emplace_back(createOpParamNameEPSGCode(
6468
32.8k
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
6469
32.8k
            values.emplace_back(createLength(tz, uom_translation));
6470
6471
32.8k
            if (!rx.empty()) {
6472
                // Helmert 7-, 8-, 10- or 15- parameter cases
6473
3.91k
                auto uom_rotation = d->createUnitOfMeasure(
6474
3.91k
                    rotation_uom_auth_name, rotation_uom_code);
6475
6476
3.91k
                parameters.emplace_back(createOpParamNameEPSGCode(
6477
3.91k
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
6478
3.91k
                values.emplace_back(createAngle(rx, uom_rotation));
6479
6480
3.91k
                parameters.emplace_back(createOpParamNameEPSGCode(
6481
3.91k
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
6482
3.91k
                values.emplace_back(createAngle(ry, uom_rotation));
6483
6484
3.91k
                parameters.emplace_back(createOpParamNameEPSGCode(
6485
3.91k
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
6486
3.91k
                values.emplace_back(createAngle(rz, uom_rotation));
6487
6488
3.91k
                auto uom_scale_difference =
6489
3.91k
                    scale_difference_uom_auth_name.empty()
6490
3.91k
                        ? common::UnitOfMeasure::NONE
6491
3.91k
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
6492
3.91k
                                                 scale_difference_uom_code);
6493
6494
3.91k
                parameters.emplace_back(createOpParamNameEPSGCode(
6495
3.91k
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
6496
3.91k
                values.emplace_back(operation::ParameterValue::create(
6497
3.91k
                    common::Scale(c_locale_stod(scale_difference),
6498
3.91k
                                  uom_scale_difference)));
6499
3.91k
            }
6500
6501
32.8k
            if (!rate_tx.empty()) {
6502
                // Helmert 15-parameter
6503
6504
839
                auto uom_rate_translation = d->createUnitOfMeasure(
6505
839
                    rate_translation_uom_auth_name, rate_translation_uom_code);
6506
6507
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6508
839
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
6509
839
                values.emplace_back(
6510
839
                    createLength(rate_tx, uom_rate_translation));
6511
6512
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6513
839
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6514
839
                values.emplace_back(
6515
839
                    createLength(rate_ty, uom_rate_translation));
6516
6517
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6518
839
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6519
839
                values.emplace_back(
6520
839
                    createLength(rate_tz, uom_rate_translation));
6521
6522
839
                auto uom_rate_rotation = d->createUnitOfMeasure(
6523
839
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6524
6525
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6526
839
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6527
839
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6528
6529
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6530
839
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6531
839
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6532
6533
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6534
839
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6535
839
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6536
6537
839
                auto uom_rate_scale_difference =
6538
839
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6539
839
                                           rate_scale_difference_uom_code);
6540
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6541
839
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6542
839
                values.emplace_back(operation::ParameterValue::create(
6543
839
                    common::Scale(c_locale_stod(rate_scale_difference),
6544
839
                                  uom_rate_scale_difference)));
6545
6546
839
                parameters.emplace_back(createOpParamNameEPSGCode(
6547
839
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6548
839
                values.emplace_back(operation::ParameterValue::create(
6549
839
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6550
32.0k
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6551
                // Helmert 8-parameter
6552
175
                parameters.emplace_back(createOpParamNameEPSGCode(
6553
175
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6554
175
                values.emplace_back(operation::ParameterValue::create(
6555
175
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6556
31.8k
            } else if (!px.empty()) {
6557
                // Molodensky-Badekas case
6558
53
                auto uom_pivot =
6559
53
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6560
6561
53
                parameters.emplace_back(createOpParamNameEPSGCode(
6562
53
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6563
53
                values.emplace_back(createLength(px, uom_pivot));
6564
6565
53
                parameters.emplace_back(createOpParamNameEPSGCode(
6566
53
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6567
53
                values.emplace_back(createLength(py, uom_pivot));
6568
6569
53
                parameters.emplace_back(createOpParamNameEPSGCode(
6570
53
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6571
53
                values.emplace_back(createLength(pz, uom_pivot));
6572
53
            }
6573
6574
32.8k
            auto props = d->createPropertiesSearchUsages(
6575
32.8k
                type, code, name, deprecated, description);
6576
32.8k
            if (!operation_version.empty()) {
6577
31.9k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6578
31.9k
                          operation_version);
6579
31.9k
            }
6580
6581
32.8k
            auto propsMethod =
6582
32.8k
                util::PropertyMap()
6583
32.8k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6584
32.8k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6585
32.8k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6586
6587
32.8k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6588
32.8k
            if (!accuracy.empty() && accuracy != "999.0") {
6589
32.6k
                accuracies.emplace_back(
6590
32.6k
                    metadata::PositionalAccuracy::create(accuracy));
6591
32.6k
            }
6592
32.8k
            return operation::Transformation::create(
6593
32.8k
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6594
32.8k
                values, accuracies);
6595
6596
32.8k
        } catch (const std::exception &ex) {
6597
0
            throw buildFactoryException("transformation", d->authority(), code,
6598
0
                                        ex);
6599
0
        }
6600
32.8k
    }
6601
6602
150k
    if (type == "grid_transformation") {
6603
111k
        auto res = d->runWithCodeParam(
6604
111k
            "SELECT name, description, "
6605
111k
            "method_auth_name, method_code, method_name, "
6606
111k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6607
111k
            "target_crs_code, "
6608
111k
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6609
111k
            "grid_name, "
6610
111k
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6611
111k
            "grid2_name, "
6612
111k
            "param1_auth_name, param1_code, param1_name, param1_value, "
6613
111k
            "param1_uom_auth_name, param1_uom_code, "
6614
111k
            "param2_auth_name, param2_code, param2_name, param2_value, "
6615
111k
            "param2_uom_auth_name, param2_uom_code, "
6616
111k
            "interpolation_crs_auth_name, interpolation_crs_code, "
6617
111k
            "operation_version, deprecated FROM "
6618
111k
            "grid_transformation WHERE auth_name = ? AND code = ?",
6619
111k
            code);
6620
111k
        if (res.empty()) {
6621
            // shouldn't happen if foreign keys are OK
6622
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6623
0
                                               d->authority(), code);
6624
0
        }
6625
111k
        try {
6626
111k
            const auto &row = res.front();
6627
111k
            size_t idx = 0;
6628
111k
            const auto &name = row[idx++];
6629
111k
            const auto &description = row[idx++];
6630
111k
            const auto &method_auth_name = row[idx++];
6631
111k
            const auto &method_code = row[idx++];
6632
111k
            const auto &method_name = row[idx++];
6633
111k
            const auto &source_crs_auth_name = row[idx++];
6634
111k
            const auto &source_crs_code = row[idx++];
6635
111k
            const auto &target_crs_auth_name = row[idx++];
6636
111k
            const auto &target_crs_code = row[idx++];
6637
111k
            const auto &accuracy = row[idx++];
6638
111k
            const auto &grid_param_auth_name = row[idx++];
6639
111k
            const auto &grid_param_code = row[idx++];
6640
111k
            const auto &grid_param_name = row[idx++];
6641
111k
            const auto &grid_name = row[idx++];
6642
111k
            const auto &grid2_param_auth_name = row[idx++];
6643
111k
            const auto &grid2_param_code = row[idx++];
6644
111k
            const auto &grid2_param_name = row[idx++];
6645
111k
            const auto &grid2_name = row[idx++];
6646
111k
            std::vector<operation::OperationParameterNNPtr> parameters;
6647
111k
            std::vector<operation::ParameterValueNNPtr> values;
6648
6649
111k
            parameters.emplace_back(operation::OperationParameter::create(
6650
111k
                util::PropertyMap()
6651
111k
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6652
111k
                    .set(metadata::Identifier::CODESPACE_KEY,
6653
111k
                         grid_param_auth_name)
6654
111k
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6655
111k
            values.emplace_back(
6656
111k
                operation::ParameterValue::createFilename(grid_name));
6657
111k
            if (!grid2_name.empty()) {
6658
103k
                parameters.emplace_back(operation::OperationParameter::create(
6659
103k
                    util::PropertyMap()
6660
103k
                        .set(common::IdentifiedObject::NAME_KEY,
6661
103k
                             grid2_param_name)
6662
103k
                        .set(metadata::Identifier::CODESPACE_KEY,
6663
103k
                             grid2_param_auth_name)
6664
103k
                        .set(metadata::Identifier::CODE_KEY,
6665
103k
                             grid2_param_code)));
6666
103k
                values.emplace_back(
6667
103k
                    operation::ParameterValue::createFilename(grid2_name));
6668
103k
            }
6669
6670
111k
            const size_t base_param_idx = idx;
6671
111k
            constexpr size_t N_MAX_PARAMS_GRID_TRANSFORMATION = 2;
6672
111k
            for (size_t i = 0; i < N_MAX_PARAMS_GRID_TRANSFORMATION; ++i) {
6673
111k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6674
111k
                if (param_auth_name.empty()) {
6675
111k
                    break;
6676
111k
                }
6677
86
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6678
86
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6679
86
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6680
86
                const auto &param_uom_auth_name =
6681
86
                    row[base_param_idx + i * 6 + 4];
6682
86
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6683
86
                parameters.emplace_back(operation::OperationParameter::create(
6684
86
                    util::PropertyMap()
6685
86
                        .set(metadata::Identifier::CODESPACE_KEY,
6686
86
                             param_auth_name)
6687
86
                        .set(metadata::Identifier::CODE_KEY, param_code)
6688
86
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6689
86
                std::string normalized_uom_code(param_uom_code);
6690
86
                const double normalized_value = normalizeMeasure(
6691
86
                    param_uom_code, param_value, normalized_uom_code);
6692
86
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6693
86
                                                  normalized_uom_code);
6694
86
                values.emplace_back(operation::ParameterValue::create(
6695
86
                    common::Measure(normalized_value, uom)));
6696
86
            }
6697
111k
            idx = base_param_idx + 6 * N_MAX_PARAMS_GRID_TRANSFORMATION;
6698
6699
111k
            const auto &interpolation_crs_auth_name = row[idx++];
6700
111k
            const auto &interpolation_crs_code = row[idx++];
6701
111k
            const auto &operation_version = row[idx++];
6702
111k
            const auto &deprecated_str = row[idx++];
6703
111k
            const bool deprecated = deprecated_str == "1";
6704
111k
            assert(idx == row.size());
6705
6706
111k
            auto sourceCRS =
6707
111k
                d->createFactory(source_crs_auth_name)
6708
111k
                    ->createCoordinateReferenceSystem(source_crs_code);
6709
111k
            auto targetCRS =
6710
111k
                d->createFactory(target_crs_auth_name)
6711
111k
                    ->createCoordinateReferenceSystem(target_crs_code);
6712
111k
            auto interpolationCRS =
6713
111k
                interpolation_crs_auth_name.empty()
6714
111k
                    ? nullptr
6715
111k
                    : d->createFactory(interpolation_crs_auth_name)
6716
164
                          ->createCoordinateReferenceSystem(
6717
164
                              interpolation_crs_code)
6718
164
                          .as_nullable();
6719
6720
111k
            auto props = d->createPropertiesSearchUsages(
6721
111k
                type, code, name, deprecated, description);
6722
111k
            if (!operation_version.empty()) {
6723
111k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6724
111k
                          operation_version);
6725
111k
            }
6726
111k
            auto propsMethod =
6727
111k
                util::PropertyMap()
6728
111k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6729
111k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6730
111k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6731
6732
111k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6733
111k
            if (!accuracy.empty() && accuracy != "999.0") {
6734
111k
                accuracies.emplace_back(
6735
111k
                    metadata::PositionalAccuracy::create(accuracy));
6736
111k
            }
6737
6738
            // A bit fragile to detect the operation type with the method name,
6739
            // but not worth changing the database model
6740
111k
            if (starts_with(method_name, "Point motion")) {
6741
12
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6742
0
                    throw operation::InvalidOperation(
6743
0
                        "source_crs and target_crs should be the same for a "
6744
0
                        "PointMotionOperation");
6745
0
                }
6746
6747
12
                auto pmo = operation::PointMotionOperation::create(
6748
12
                    props, sourceCRS, propsMethod, parameters, values,
6749
12
                    accuracies);
6750
12
                if (usePROJAlternativeGridNames) {
6751
12
                    return pmo->substitutePROJAlternativeGridNames(
6752
12
                        d->context());
6753
12
                }
6754
0
                return pmo;
6755
12
            }
6756
6757
111k
            auto transf = operation::Transformation::create(
6758
111k
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6759
111k
                parameters, values, accuracies);
6760
111k
            if (usePROJAlternativeGridNames) {
6761
111k
                return transf->substitutePROJAlternativeGridNames(d->context());
6762
111k
            }
6763
0
            return transf;
6764
6765
111k
        } catch (const std::exception &ex) {
6766
0
            throw buildFactoryException("transformation", d->authority(), code,
6767
0
                                        ex);
6768
0
        }
6769
111k
    }
6770
6771
39.1k
    if (type == "other_transformation") {
6772
693
        std::ostringstream buffer;
6773
693
        buffer.imbue(std::locale::classic());
6774
693
        buffer
6775
693
            << "SELECT name, description, "
6776
693
               "method_auth_name, method_code, method_name, "
6777
693
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6778
693
               "target_crs_code, "
6779
693
               "grid_param_auth_name, grid_param_code, grid_param_name, "
6780
693
               "grid_name, "
6781
693
               "interpolation_crs_auth_name, interpolation_crs_code, "
6782
693
               "operation_version, accuracy, deprecated";
6783
693
        constexpr int N_MAX_PARAMS_OTHER_TRANSFORMATION = 9;
6784
6.93k
        for (size_t i = 1; i <= N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6785
6.23k
            buffer << ", param" << i << "_auth_name";
6786
6.23k
            buffer << ", param" << i << "_code";
6787
6.23k
            buffer << ", param" << i << "_name";
6788
6.23k
            buffer << ", param" << i << "_value";
6789
6.23k
            buffer << ", param" << i << "_uom_auth_name";
6790
6.23k
            buffer << ", param" << i << "_uom_code";
6791
6.23k
        }
6792
693
        buffer << " FROM other_transformation "
6793
693
                  "WHERE auth_name = ? AND code = ?";
6794
6795
693
        auto res = d->runWithCodeParam(buffer.str(), code);
6796
693
        if (res.empty()) {
6797
            // shouldn't happen if foreign keys are OK
6798
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6799
0
                                               d->authority(), code);
6800
0
        }
6801
693
        try {
6802
693
            const auto &row = res.front();
6803
693
            size_t idx = 0;
6804
693
            const auto &name = row[idx++];
6805
693
            const auto &description = row[idx++];
6806
693
            const auto &method_auth_name = row[idx++];
6807
693
            const auto &method_code = row[idx++];
6808
693
            const auto &method_name = row[idx++];
6809
693
            const auto &source_crs_auth_name = row[idx++];
6810
693
            const auto &source_crs_code = row[idx++];
6811
693
            const auto &target_crs_auth_name = row[idx++];
6812
693
            const auto &target_crs_code = row[idx++];
6813
693
            const auto &grid_param_auth_name = row[idx++];
6814
693
            const auto &grid_param_code = row[idx++];
6815
693
            const auto &grid_param_name = row[idx++];
6816
693
            const auto &grid_name = row[idx++];
6817
693
            const auto &interpolation_crs_auth_name = row[idx++];
6818
693
            const auto &interpolation_crs_code = row[idx++];
6819
693
            const auto &operation_version = row[idx++];
6820
693
            const auto &accuracy = row[idx++];
6821
693
            const auto &deprecated_str = row[idx++];
6822
693
            const bool deprecated = deprecated_str == "1";
6823
6824
693
            const size_t base_param_idx = idx;
6825
693
            std::vector<operation::OperationParameterNNPtr> parameters;
6826
693
            std::vector<operation::ParameterValueNNPtr> values;
6827
1.38k
            for (size_t i = 0; i < N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6828
1.33k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6829
1.33k
                if (param_auth_name.empty()) {
6830
640
                    break;
6831
640
                }
6832
693
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6833
693
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6834
693
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6835
693
                const auto &param_uom_auth_name =
6836
693
                    row[base_param_idx + i * 6 + 4];
6837
693
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6838
6839
693
                parameters.emplace_back(operation::OperationParameter::create(
6840
693
                    util::PropertyMap()
6841
693
                        .set(metadata::Identifier::CODESPACE_KEY,
6842
693
                             param_auth_name)
6843
693
                        .set(metadata::Identifier::CODE_KEY, param_code)
6844
693
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6845
693
                std::string normalized_uom_code(param_uom_code);
6846
693
                const double normalized_value = normalizeMeasure(
6847
693
                    param_uom_code, param_value, normalized_uom_code);
6848
693
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6849
693
                                                  normalized_uom_code);
6850
693
                values.emplace_back(operation::ParameterValue::create(
6851
693
                    common::Measure(normalized_value, uom)));
6852
693
            }
6853
693
            idx = base_param_idx + 6 * N_MAX_PARAMS_OTHER_TRANSFORMATION;
6854
693
            (void)idx;
6855
693
            assert(idx == row.size());
6856
6857
693
            if (!grid_name.empty()) {
6858
53
                parameters.emplace_back(operation::OperationParameter::create(
6859
53
                    util::PropertyMap()
6860
53
                        .set(common::IdentifiedObject::NAME_KEY,
6861
53
                             grid_param_name)
6862
53
                        .set(metadata::Identifier::CODESPACE_KEY,
6863
53
                             grid_param_auth_name)
6864
53
                        .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6865
53
                values.emplace_back(
6866
53
                    operation::ParameterValue::createFilename(grid_name));
6867
53
            }
6868
6869
693
            auto sourceCRS =
6870
693
                d->createFactory(source_crs_auth_name)
6871
693
                    ->createCoordinateReferenceSystem(source_crs_code);
6872
693
            auto targetCRS =
6873
693
                d->createFactory(target_crs_auth_name)
6874
693
                    ->createCoordinateReferenceSystem(target_crs_code);
6875
693
            auto interpolationCRS =
6876
693
                interpolation_crs_auth_name.empty()
6877
693
                    ? nullptr
6878
693
                    : d->createFactory(interpolation_crs_auth_name)
6879
57
                          ->createCoordinateReferenceSystem(
6880
57
                              interpolation_crs_code)
6881
57
                          .as_nullable();
6882
6883
693
            auto props = d->createPropertiesSearchUsages(
6884
693
                type, code, name, deprecated, description);
6885
693
            if (!operation_version.empty()) {
6886
633
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6887
633
                          operation_version);
6888
633
            }
6889
6890
693
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6891
693
            if (!accuracy.empty() && accuracy != "999.0") {
6892
692
                accuracies.emplace_back(
6893
692
                    metadata::PositionalAccuracy::create(accuracy));
6894
692
            }
6895
6896
693
            if (method_auth_name == "PROJ") {
6897
475
                if (method_code == "PROJString") {
6898
475
                    auto op = operation::SingleOperation::createPROJBased(
6899
475
                        props, method_name, sourceCRS, targetCRS, accuracies);
6900
475
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6901
475
                    return op;
6902
475
                } else if (method_code == "WKT") {
6903
0
                    auto op = util::nn_dynamic_pointer_cast<
6904
0
                        operation::CoordinateOperation>(
6905
0
                        WKTParser().createFromWKT(method_name));
6906
0
                    if (!op) {
6907
0
                        throw FactoryException("WKT string does not express a "
6908
0
                                               "coordinate operation");
6909
0
                    }
6910
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6911
0
                    return NN_NO_CHECK(op);
6912
0
                }
6913
475
            }
6914
6915
218
            auto propsMethod =
6916
218
                util::PropertyMap()
6917
218
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6918
218
                    .set(metadata::Identifier::CODE_KEY, method_code)
6919
218
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6920
6921
218
            if (method_auth_name == metadata::Identifier::EPSG) {
6922
218
                int method_code_int = std::atoi(method_code.c_str());
6923
218
                if (operation::isAxisOrderReversal(method_code_int) ||
6924
184
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6925
184
                    method_code_int ==
6926
184
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6927
184
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6928
35
                    auto op = operation::Conversion::create(props, propsMethod,
6929
35
                                                            parameters, values);
6930
35
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6931
35
                    return op;
6932
35
                }
6933
218
            }
6934
183
            auto transf = operation::Transformation::create(
6935
183
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6936
183
                parameters, values, accuracies);
6937
183
            if (usePROJAlternativeGridNames) {
6938
183
                return transf->substitutePROJAlternativeGridNames(d->context());
6939
183
            }
6940
0
            return transf;
6941
6942
183
        } catch (const std::exception &ex) {
6943
0
            throw buildFactoryException("transformation", d->authority(), code,
6944
0
                                        ex);
6945
0
        }
6946
693
    }
6947
6948
38.4k
    if (allowConcatenated && type == "concatenated_operation") {
6949
38.4k
        auto res = d->runWithCodeParam(
6950
38.4k
            "SELECT name, description, "
6951
38.4k
            "source_crs_auth_name, source_crs_code, "
6952
38.4k
            "target_crs_auth_name, target_crs_code, "
6953
38.4k
            "accuracy, "
6954
38.4k
            "operation_version, deprecated FROM "
6955
38.4k
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6956
38.4k
            code);
6957
38.4k
        if (res.empty()) {
6958
            // shouldn't happen if foreign keys are OK
6959
0
            throw NoSuchAuthorityCodeException(
6960
0
                "concatenated_operation not found", d->authority(), code);
6961
0
        }
6962
6963
38.4k
        auto resSteps = d->runWithCodeParam(
6964
38.4k
            "SELECT step_auth_name, step_code, step_direction FROM "
6965
38.4k
            "concatenated_operation_step WHERE operation_auth_name = ? "
6966
38.4k
            "AND operation_code = ? ORDER BY step_number",
6967
38.4k
            code);
6968
6969
38.4k
        try {
6970
38.4k
            const auto &row = res.front();
6971
38.4k
            size_t idx = 0;
6972
38.4k
            const auto &name = row[idx++];
6973
38.4k
            const auto &description = row[idx++];
6974
38.4k
            const auto &source_crs_auth_name = row[idx++];
6975
38.4k
            const auto &source_crs_code = row[idx++];
6976
38.4k
            const auto &target_crs_auth_name = row[idx++];
6977
38.4k
            const auto &target_crs_code = row[idx++];
6978
38.4k
            const auto &accuracy = row[idx++];
6979
38.4k
            const auto &operation_version = row[idx++];
6980
38.4k
            const auto &deprecated_str = row[idx++];
6981
38.4k
            const bool deprecated = deprecated_str == "1";
6982
6983
38.4k
            std::vector<operation::CoordinateOperationNNPtr> operations;
6984
38.4k
            size_t countExplicitDirection = 0;
6985
78.1k
            for (const auto &rowStep : resSteps) {
6986
78.1k
                const auto &step_auth_name = rowStep[0];
6987
78.1k
                const auto &step_code = rowStep[1];
6988
78.1k
                const auto &step_direction = rowStep[2];
6989
78.1k
                auto stepOp =
6990
78.1k
                    d->createFactory(step_auth_name)
6991
78.1k
                        ->createCoordinateOperation(step_code, false,
6992
78.1k
                                                    usePROJAlternativeGridNames,
6993
78.1k
                                                    std::string());
6994
78.1k
                if (step_direction == "forward") {
6995
4.09k
                    ++countExplicitDirection;
6996
4.09k
                    operations.push_back(std::move(stepOp));
6997
74.0k
                } else if (step_direction == "reverse") {
6998
115
                    ++countExplicitDirection;
6999
115
                    operations.push_back(stepOp->inverse());
7000
73.9k
                } else {
7001
73.9k
                    operations.push_back(std::move(stepOp));
7002
73.9k
                }
7003
78.1k
            }
7004
7005
38.4k
            if (countExplicitDirection > 0 &&
7006
1.80k
                countExplicitDirection != resSteps.size()) {
7007
0
                throw FactoryException("not all steps have a defined direction "
7008
0
                                       "for concatenated operation " +
7009
0
                                       code);
7010
0
            }
7011
7012
38.4k
            const bool fixDirectionAllowed = (countExplicitDirection == 0);
7013
38.4k
            operation::ConcatenatedOperation::fixSteps(
7014
38.4k
                d->createFactory(source_crs_auth_name)
7015
38.4k
                    ->createCoordinateReferenceSystem(source_crs_code),
7016
38.4k
                d->createFactory(target_crs_auth_name)
7017
38.4k
                    ->createCoordinateReferenceSystem(target_crs_code),
7018
38.4k
                operations, d->context(), fixDirectionAllowed);
7019
7020
38.4k
            auto props = d->createPropertiesSearchUsages(
7021
38.4k
                type, code, name, deprecated, description);
7022
38.4k
            if (!operation_version.empty()) {
7023
36.8k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
7024
36.8k
                          operation_version);
7025
36.8k
            }
7026
7027
38.4k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
7028
38.4k
            if (!accuracy.empty()) {
7029
38.4k
                if (accuracy != "999.0") {
7030
38.4k
                    accuracies.emplace_back(
7031
38.4k
                        metadata::PositionalAccuracy::create(accuracy));
7032
38.4k
                }
7033
38.4k
            } else {
7034
                // Try to compute a reasonable accuracy from the members
7035
13
                double totalAcc = -1;
7036
13
                try {
7037
35
                    for (const auto &op : operations) {
7038
35
                        auto accs = op->coordinateOperationAccuracies();
7039
35
                        if (accs.size() == 1) {
7040
15
                            double acc = c_locale_stod(accs[0]->value());
7041
15
                            if (totalAcc < 0) {
7042
6
                                totalAcc = acc;
7043
9
                            } else {
7044
9
                                totalAcc += acc;
7045
9
                            }
7046
20
                        } else if (dynamic_cast<const operation::Conversion *>(
7047
20
                                       op.get())) {
7048
                            // A conversion is perfectly accurate.
7049
16
                            if (totalAcc < 0) {
7050
7
                                totalAcc = 0;
7051
7
                            }
7052
16
                        } else {
7053
4
                            totalAcc = -1;
7054
4
                            break;
7055
4
                        }
7056
35
                    }
7057
13
                    if (totalAcc >= 0) {
7058
9
                        accuracies.emplace_back(
7059
9
                            metadata::PositionalAccuracy::create(
7060
9
                                toString(totalAcc)));
7061
9
                    }
7062
13
                } catch (const std::exception &) {
7063
0
                }
7064
13
            }
7065
38.4k
            return operation::ConcatenatedOperation::create(props, operations,
7066
38.4k
                                                            accuracies);
7067
7068
38.4k
        } catch (const std::exception &ex) {
7069
0
            throw buildFactoryException("transformation", d->authority(), code,
7070
0
                                        ex);
7071
0
        }
7072
38.4k
    }
7073
7074
0
    throw FactoryException("unhandled coordinate operation type: " + type);
7075
38.4k
}
7076
7077
// ---------------------------------------------------------------------------
7078
7079
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7080
 *
7081
 * The list is ordered with preferred operations first. No attempt is made
7082
 * at inferring operations that are not explicitly in the database.
7083
 *
7084
 * Deprecated operations are rejected.
7085
 *
7086
 * @param sourceCRSCode Source CRS code allocated by authority.
7087
 * @param targetCRSCode Source CRS code allocated by authority.
7088
 * @return list of coordinate operations
7089
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7090
 * @throw FactoryException in case of other errors.
7091
 */
7092
7093
std::vector<operation::CoordinateOperationNNPtr>
7094
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7095
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
7096
0
    return createFromCoordinateReferenceSystemCodes(
7097
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
7098
0
        false, false, false);
7099
0
}
7100
7101
// ---------------------------------------------------------------------------
7102
7103
/** \brief Returns a list of geoid models available for that crs
7104
 *
7105
 * The list includes the geoid models connected directly with the crs,
7106
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
7107
 *
7108
 * @param code crs code allocated by authority.
7109
 * @return list of geoid model names
7110
 * @throw FactoryException in case of error.
7111
 */
7112
7113
std::list<std::string>
7114
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
7115
7116
0
    ListOfParams params;
7117
0
    std::string sql;
7118
0
    sql += "SELECT DISTINCT GM0.name "
7119
0
           "  FROM geoid_model GM0 "
7120
0
           "INNER JOIN grid_transformation GT0 "
7121
0
           "  ON  GT0.code = GM0.operation_code "
7122
0
           "  AND GT0.auth_name = GM0.operation_auth_name "
7123
0
           "  AND GT0.deprecated = 0 "
7124
0
           "INNER JOIN vertical_crs VC0 "
7125
0
           "  ON VC0.code = GT0.target_crs_code "
7126
0
           "  AND VC0.auth_name = GT0.target_crs_auth_name "
7127
0
           "INNER JOIN vertical_crs VC1 "
7128
0
           "  ON VC1.datum_code = VC0.datum_code "
7129
0
           "  AND VC1.datum_auth_name = VC0.datum_auth_name "
7130
0
           "  AND VC1.code = ? ";
7131
0
    params.emplace_back(code);
7132
0
    if (d->hasAuthorityRestriction()) {
7133
0
        sql += " AND GT0.target_crs_auth_name = ? ";
7134
0
        params.emplace_back(d->authority());
7135
0
    }
7136
0
    sql += " ORDER BY 1 ";
7137
7138
0
    auto sqlRes = d->run(sql, params);
7139
0
    std::list<std::string> res;
7140
0
    for (const auto &row : sqlRes) {
7141
0
        res.push_back(row[0]);
7142
0
    }
7143
0
    return res;
7144
0
}
7145
7146
// ---------------------------------------------------------------------------
7147
7148
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7149
 *
7150
 * The list is ordered with preferred operations first. No attempt is made
7151
 * at inferring operations that are not explicitly in the database (see
7152
 * createFromCRSCodesWithIntermediates() for that), and only
7153
 * source -> target operations are searched (i.e. if target -> source is
7154
 * present, you need to call this method with the arguments reversed, and apply
7155
 * the reverse transformations).
7156
 *
7157
 * Deprecated operations are rejected.
7158
 *
7159
 * If getAuthority() returns empty, then coordinate operations from all
7160
 * authorities are considered.
7161
 *
7162
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7163
 * @param sourceCRSCode Source CRS code allocated by authority
7164
 * sourceCRSAuthName.
7165
 * @param targetCRSAuthName Authority name of targetCRSCode
7166
 * @param targetCRSCode Source CRS code allocated by authority
7167
 * targetCRSAuthName.
7168
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7169
 * should be substituted to the official grid names.
7170
 * @param discardIfMissingGrid Whether coordinate operations that reference
7171
 * missing grids should be removed from the result set.
7172
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7173
 * as available (typically when network is enabled).
7174
 * @param discardSuperseded Whether coordinate operations that are superseded
7175
 * (but not deprecated) should be removed from the result set.
7176
 * @param tryReverseOrder whether to search in the reverse order too (and thus
7177
 * inverse results found that way)
7178
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
7179
 * intersectingExtent2 should be honored in a strict way.
7180
 * @param intersectingExtent1 Optional extent that the resulting operations
7181
 * must intersect.
7182
 * @param intersectingExtent2 Optional extent that the resulting operations
7183
 * must intersect.
7184
 * @return list of coordinate operations
7185
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7186
 * @throw FactoryException in case of other errors.
7187
 */
7188
7189
std::vector<operation::CoordinateOperationNNPtr>
7190
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7191
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7192
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7193
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7194
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7195
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
7196
    const metadata::ExtentPtr &intersectingExtent1,
7197
192k
    const metadata::ExtentPtr &intersectingExtent2) const {
7198
7199
192k
    auto cacheKey(d->authority());
7200
192k
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
7201
192k
    cacheKey += sourceCRSCode;
7202
192k
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
7203
192k
    cacheKey += targetCRSCode;
7204
192k
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
7205
192k
    cacheKey += (discardIfMissingGrid ? '1' : '0');
7206
192k
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
7207
192k
    cacheKey += (discardSuperseded ? '1' : '0');
7208
192k
    cacheKey += (tryReverseOrder ? '1' : '0');
7209
192k
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
7210
385k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7211
385k
        if (extent) {
7212
56.6k
            const auto &geogExtent = extent->geographicElements();
7213
56.6k
            if (geogExtent.size() == 1) {
7214
56.6k
                auto bbox =
7215
56.6k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7216
56.6k
                        geogExtent[0].get());
7217
56.6k
                if (bbox) {
7218
56.6k
                    cacheKey += toString(bbox->southBoundLatitude());
7219
56.6k
                    cacheKey += toString(bbox->westBoundLongitude());
7220
56.6k
                    cacheKey += toString(bbox->northBoundLatitude());
7221
56.6k
                    cacheKey += toString(bbox->eastBoundLongitude());
7222
56.6k
                }
7223
56.6k
            }
7224
56.6k
        }
7225
385k
    }
7226
7227
192k
    std::vector<operation::CoordinateOperationNNPtr> list;
7228
7229
192k
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
7230
151k
        return list;
7231
151k
    }
7232
7233
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
7234
    // In which case use the conversion of the ProjectedCRS
7235
40.7k
    if (!targetCRSAuthName.empty()) {
7236
40.7k
        auto targetFactory = d->createFactory(targetCRSAuthName);
7237
40.7k
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
7238
40.7k
                                        targetCRSCode);
7239
40.7k
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
7240
40.7k
            cacheKeyProjectedCRS);
7241
40.7k
        crs::ProjectedCRSPtr targetProjCRS;
7242
40.7k
        if (crs) {
7243
40.7k
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
7244
40.7k
        } else {
7245
0
            const auto sqlRes =
7246
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
7247
0
            if (!sqlRes.empty()) {
7248
0
                try {
7249
0
                    targetProjCRS =
7250
0
                        targetFactory->d
7251
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
7252
0
                            .as_nullable();
7253
0
                } catch (const std::exception &) {
7254
0
                }
7255
0
            }
7256
0
        }
7257
40.7k
        if (targetProjCRS) {
7258
3
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
7259
3
            if (sourceCRSAuthName.empty() ||
7260
3
                (!baseIds.empty() &&
7261
3
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
7262
3
                 baseIds.front()->code() == sourceCRSCode)) {
7263
0
                bool ok = true;
7264
0
                auto conv = targetProjCRS->derivingConversion();
7265
0
                if (d->hasAuthorityRestriction()) {
7266
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
7267
0
                         d->authority();
7268
0
                }
7269
0
                if (ok) {
7270
0
                    list.emplace_back(conv);
7271
0
                    d->context()->d->cache(cacheKey, list);
7272
0
                    return list;
7273
0
                }
7274
0
            }
7275
3
        }
7276
40.7k
    }
7277
7278
40.7k
    std::string sql;
7279
40.7k
    if (discardSuperseded) {
7280
40.7k
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
7281
40.7k
              "cov.target_crs_auth_name, cov.target_crs_code, "
7282
40.7k
              "cov.auth_name, cov.code, cov.table_name, "
7283
40.7k
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7284
40.7k
              "extent.east_lon, "
7285
40.7k
              "ss.replacement_auth_name, ss.replacement_code, "
7286
40.7k
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
7287
40.7k
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
7288
40.7k
              "FROM "
7289
40.7k
              "coordinate_operation_view cov "
7290
40.7k
              "JOIN usage ON "
7291
40.7k
              "usage.object_table_name = cov.table_name AND "
7292
40.7k
              "usage.object_auth_name = cov.auth_name AND "
7293
40.7k
              "usage.object_code = cov.code "
7294
40.7k
              "JOIN extent "
7295
40.7k
              "ON extent.auth_name = usage.extent_auth_name AND "
7296
40.7k
              "extent.code = usage.extent_code "
7297
40.7k
              "LEFT JOIN supersession ss ON "
7298
40.7k
              "ss.superseded_table_name = cov.table_name AND "
7299
40.7k
              "ss.superseded_auth_name = cov.auth_name AND "
7300
40.7k
              "ss.superseded_code = cov.code AND "
7301
40.7k
              "ss.superseded_table_name = ss.replacement_table_name AND "
7302
40.7k
              "ss.same_source_target_crs = 1 "
7303
40.7k
              "LEFT JOIN grid_transformation gt ON "
7304
40.7k
              "gt.auth_name = ss.replacement_auth_name AND "
7305
40.7k
              "gt.code = ss.replacement_code "
7306
40.7k
              "LEFT JOIN grid_alternatives ga ON "
7307
40.7k
              "ga.original_grid_name = gt.grid_name "
7308
40.7k
              "WHERE ";
7309
40.7k
    } else {
7310
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
7311
0
              "target_crs_auth_name, target_crs_code, "
7312
0
              "cov.auth_name, cov.code, cov.table_name, "
7313
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7314
0
              "extent.east_lon "
7315
0
              "FROM "
7316
0
              "coordinate_operation_view cov "
7317
0
              "JOIN usage ON "
7318
0
              "usage.object_table_name = cov.table_name AND "
7319
0
              "usage.object_auth_name = cov.auth_name AND "
7320
0
              "usage.object_code = cov.code "
7321
0
              "JOIN extent "
7322
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7323
0
              "extent.code = usage.extent_code "
7324
0
              "WHERE ";
7325
0
    }
7326
40.7k
    ListOfParams params;
7327
40.7k
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
7328
40.6k
        if (tryReverseOrder) {
7329
40.6k
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7330
40.6k
                   "AND "
7331
40.6k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
7332
40.6k
                   "OR "
7333
40.6k
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7334
40.6k
                   "AND "
7335
40.6k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
7336
40.6k
                   "AND ";
7337
40.6k
            params.emplace_back(sourceCRSAuthName);
7338
40.6k
            params.emplace_back(sourceCRSCode);
7339
40.6k
            params.emplace_back(targetCRSAuthName);
7340
40.6k
            params.emplace_back(targetCRSCode);
7341
40.6k
            params.emplace_back(targetCRSAuthName);
7342
40.6k
            params.emplace_back(targetCRSCode);
7343
40.6k
            params.emplace_back(sourceCRSAuthName);
7344
40.6k
            params.emplace_back(sourceCRSCode);
7345
40.6k
        } else {
7346
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7347
0
                   "AND "
7348
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7349
0
                   "AND ";
7350
0
            params.emplace_back(sourceCRSAuthName);
7351
0
            params.emplace_back(sourceCRSCode);
7352
0
            params.emplace_back(targetCRSAuthName);
7353
0
            params.emplace_back(targetCRSCode);
7354
0
        }
7355
40.6k
    } else if (!sourceCRSAuthName.empty()) {
7356
0
        if (tryReverseOrder) {
7357
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7358
0
                   ")OR "
7359
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7360
0
                   " AND ";
7361
0
            params.emplace_back(sourceCRSAuthName);
7362
0
            params.emplace_back(sourceCRSCode);
7363
0
            params.emplace_back(sourceCRSAuthName);
7364
0
            params.emplace_back(sourceCRSCode);
7365
0
        } else {
7366
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7367
0
                   "AND ";
7368
0
            params.emplace_back(sourceCRSAuthName);
7369
0
            params.emplace_back(sourceCRSCode);
7370
0
        }
7371
24
    } else if (!targetCRSAuthName.empty()) {
7372
24
        if (tryReverseOrder) {
7373
24
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
7374
24
                   " OR "
7375
24
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7376
24
                   " AND ";
7377
24
            params.emplace_back(targetCRSAuthName);
7378
24
            params.emplace_back(targetCRSCode);
7379
24
            params.emplace_back(targetCRSAuthName);
7380
24
            params.emplace_back(targetCRSCode);
7381
24
        } else {
7382
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7383
0
                   "AND ";
7384
0
            params.emplace_back(targetCRSAuthName);
7385
0
            params.emplace_back(targetCRSCode);
7386
0
        }
7387
24
    }
7388
40.7k
    sql += "cov.deprecated = 0";
7389
40.7k
    if (d->hasAuthorityRestriction()) {
7390
39.5k
        sql += " AND cov.auth_name = ?";
7391
39.5k
        params.emplace_back(d->authority());
7392
39.5k
    }
7393
40.7k
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
7394
40.7k
           "east_lon) DESC, "
7395
40.7k
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
7396
40.7k
    auto res = d->run(sql, params);
7397
40.7k
    std::set<std::pair<std::string, std::string>> setTransf;
7398
40.7k
    if (discardSuperseded) {
7399
53.0k
        for (const auto &row : res) {
7400
53.0k
            const auto &auth_name = row[4];
7401
53.0k
            const auto &code = row[5];
7402
53.0k
            setTransf.insert(
7403
53.0k
                std::pair<std::string, std::string>(auth_name, code));
7404
53.0k
        }
7405
40.7k
    }
7406
7407
    // Do a pass to determine if there are transformations that intersect
7408
    // intersectingExtent1 & intersectingExtent2
7409
40.7k
    std::vector<bool> intersectingTransformations;
7410
40.7k
    intersectingTransformations.resize(res.size());
7411
40.7k
    bool hasIntersectingTransformations = false;
7412
40.7k
    size_t i = 0;
7413
53.0k
    for (const auto &row : res) {
7414
53.0k
        size_t thisI = i;
7415
53.0k
        ++i;
7416
53.0k
        if (discardSuperseded) {
7417
53.0k
            const auto &replacement_auth_name = row[11];
7418
53.0k
            const auto &replacement_code = row[12];
7419
53.0k
            const bool replacement_is_grid_transform = row[13] == "1";
7420
53.0k
            const bool replacement_is_known_grid = row[14] == "1";
7421
53.0k
            if (!replacement_auth_name.empty() &&
7422
                // Ignore supersession if the replacement uses a unknown grid
7423
303
                !(replacement_is_grid_transform &&
7424
229
                  !replacement_is_known_grid) &&
7425
303
                setTransf.find(std::pair<std::string, std::string>(
7426
303
                    replacement_auth_name, replacement_code)) !=
7427
303
                    setTransf.end()) {
7428
                // Skip transformations that are superseded by others that got
7429
                // returned in the result set.
7430
303
                continue;
7431
303
            }
7432
53.0k
        }
7433
7434
52.7k
        bool intersecting = true;
7435
52.7k
        try {
7436
52.7k
            double south_lat = c_locale_stod(row[7]);
7437
52.7k
            double west_lon = c_locale_stod(row[8]);
7438
52.7k
            double north_lat = c_locale_stod(row[9]);
7439
52.7k
            double east_lon = c_locale_stod(row[10]);
7440
52.7k
            auto transf_extent = metadata::Extent::createFromBBOX(
7441
52.7k
                west_lon, south_lat, east_lon, north_lat);
7442
7443
52.7k
            for (const auto &extent :
7444
105k
                 {intersectingExtent1, intersectingExtent2}) {
7445
105k
                if (extent) {
7446
1.26k
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
7447
6
                        intersecting = false;
7448
6
                        break;
7449
6
                    }
7450
1.26k
                }
7451
105k
            }
7452
52.7k
        } catch (const std::exception &) {
7453
0
        }
7454
7455
52.7k
        intersectingTransformations[thisI] = intersecting;
7456
52.7k
        if (intersecting)
7457
52.7k
            hasIntersectingTransformations = true;
7458
52.7k
    }
7459
7460
    // If there are intersecting transformations, then only report those ones
7461
    // If there are no intersecting transformations, report all of them
7462
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
7463
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
7464
    // coordinates are used
7465
40.7k
    i = 0;
7466
53.0k
    for (const auto &row : res) {
7467
53.0k
        size_t thisI = i;
7468
53.0k
        ++i;
7469
53.0k
        if ((hasIntersectingTransformations ||
7470
0
             reportOnlyIntersectingTransformations) &&
7471
53.0k
            !intersectingTransformations[thisI]) {
7472
309
            continue;
7473
309
        }
7474
52.7k
        if (discardSuperseded) {
7475
52.7k
            const auto &replacement_auth_name = row[11];
7476
52.7k
            const auto &replacement_code = row[12];
7477
52.7k
            const bool replacement_is_grid_transform = row[13] == "1";
7478
52.7k
            const bool replacement_is_known_grid = row[14] == "1";
7479
52.7k
            if (!replacement_auth_name.empty() &&
7480
                // Ignore supersession if the replacement uses a unknown grid
7481
0
                !(replacement_is_grid_transform &&
7482
0
                  !replacement_is_known_grid) &&
7483
0
                setTransf.find(std::pair<std::string, std::string>(
7484
0
                    replacement_auth_name, replacement_code)) !=
7485
0
                    setTransf.end()) {
7486
                // Skip transformations that are superseded by others that got
7487
                // returned in the result set.
7488
0
                continue;
7489
0
            }
7490
52.7k
        }
7491
7492
52.7k
        const auto &source_crs_auth_name = row[0];
7493
52.7k
        const auto &source_crs_code = row[1];
7494
52.7k
        const auto &target_crs_auth_name = row[2];
7495
52.7k
        const auto &target_crs_code = row[3];
7496
52.7k
        const auto &auth_name = row[4];
7497
52.7k
        const auto &code = row[5];
7498
52.7k
        const auto &table_name = row[6];
7499
52.7k
        try {
7500
52.7k
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
7501
52.7k
                code, true, usePROJAlternativeGridNames, table_name);
7502
52.7k
            if (tryReverseOrder &&
7503
52.7k
                (!sourceCRSAuthName.empty()
7504
52.7k
                     ? (source_crs_auth_name != sourceCRSAuthName ||
7505
52.7k
                        source_crs_code != sourceCRSCode)
7506
52.7k
                     : (target_crs_auth_name != targetCRSAuthName ||
7507
33.8k
                        target_crs_code != targetCRSCode))) {
7508
33.8k
                op = op->inverse();
7509
33.8k
            }
7510
52.7k
            if (!discardIfMissingGrid ||
7511
52.7k
                !d->rejectOpDueToMissingGrid(op,
7512
52.7k
                                             considerKnownGridsAsAvailable)) {
7513
18.2k
                list.emplace_back(op);
7514
18.2k
            }
7515
52.7k
        } catch (const std::exception &e) {
7516
            // Mostly for debugging purposes when using an inconsistent
7517
            // database
7518
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7519
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7520
0
            } else {
7521
0
                throw;
7522
0
            }
7523
0
        }
7524
52.7k
    }
7525
40.7k
    d->context()->d->cache(cacheKey, list);
7526
40.7k
    return list;
7527
40.7k
}
7528
7529
// ---------------------------------------------------------------------------
7530
7531
//! @cond Doxygen_Suppress
7532
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
7533
                               const std::string &sourceCRSAuthName,
7534
                               const std::string &sourceCRSCode,
7535
                               const std::string &targetCRSAuthName,
7536
51.3k
                               const std::string &targetCRSCode) {
7537
51.3k
    auto concat =
7538
51.3k
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
7539
51.3k
    if (!concat) {
7540
33.0k
        return false;
7541
33.0k
    }
7542
18.2k
    auto ops = concat->operations();
7543
19.0k
    for (size_t i = 0; i + 1 < ops.size(); i++) {
7544
18.6k
        auto targetCRS = ops[i]->targetCRS();
7545
18.6k
        if (targetCRS) {
7546
18.6k
            const auto &ids = targetCRS->identifiers();
7547
18.6k
            if (ids.size() == 1 &&
7548
18.6k
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
7549
18.5k
                  ids[0]->code() == sourceCRSCode) ||
7550
8.42k
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7551
17.8k
                  ids[0]->code() == targetCRSCode))) {
7552
17.8k
                return true;
7553
17.8k
            }
7554
18.6k
        }
7555
18.6k
    }
7556
407
    return false;
7557
18.2k
}
7558
//! @endcond
7559
7560
// ---------------------------------------------------------------------------
7561
7562
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7563
 * using intermediate codes.
7564
 *
7565
 * The list is ordered with preferred operations first.
7566
 *
7567
 * Deprecated operations are rejected.
7568
 *
7569
 * The method will take care of considering all potential combinations (i.e.
7570
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7571
 * call it with sourceCRS and targetCRS switched)
7572
 *
7573
 * If getAuthority() returns empty, then coordinate operations from all
7574
 * authorities are considered.
7575
 *
7576
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7577
 * @param sourceCRSCode Source CRS code allocated by authority
7578
 * sourceCRSAuthName.
7579
 * @param targetCRSAuthName Authority name of targetCRSCode
7580
 * @param targetCRSCode Source CRS code allocated by authority
7581
 * targetCRSAuthName.
7582
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7583
 * should be substituted to the official grid names.
7584
 * @param discardIfMissingGrid Whether coordinate operations that reference
7585
 * missing grids should be removed from the result set.
7586
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7587
 * as available (typically when network is enabled).
7588
 * @param discardSuperseded Whether coordinate operations that are superseded
7589
 * (but not deprecated) should be removed from the result set.
7590
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7591
 * used as potential intermediate CRS. If the list is empty, the database will
7592
 * be used to find common CRS in operations involving both the source and
7593
 * target CRS.
7594
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7595
 * object considered.
7596
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7597
 * @param allowedAuthorities One or several authority name allowed for the two
7598
 * coordinate operations that are going to be searched. When this vector is
7599
 * no empty, it overrides the authority of this object. This is useful for
7600
 * example when the coordinate operations to chain belong to two different
7601
 * allowed authorities.
7602
 * @param intersectingExtent1 Optional extent that the resulting operations
7603
 * must intersect.
7604
 * @param intersectingExtent2 Optional extent that the resulting operations
7605
 * must intersect.
7606
 * @param skipIntermediateExtentIntersection When true, skip the requirement
7607
 * that the extents of the two intermediate operations must intersect each
7608
 * other. This is useful when SourceTargetCRSExtentUse::NONE is set.
7609
 * @return list of coordinate operations
7610
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7611
 * @throw FactoryException in case of other errors.
7612
 */
7613
7614
std::vector<operation::CoordinateOperationNNPtr>
7615
AuthorityFactory::createFromCRSCodesWithIntermediates(
7616
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7617
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7618
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7619
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7620
    const std::vector<std::pair<std::string, std::string>>
7621
        &intermediateCRSAuthCodes,
7622
    ObjectType allowedIntermediateObjectType,
7623
    const std::vector<std::string> &allowedAuthorities,
7624
    const metadata::ExtentPtr &intersectingExtent1,
7625
    const metadata::ExtentPtr &intersectingExtent2,
7626
12.1k
    bool skipIntermediateExtentIntersection) const {
7627
7628
12.1k
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7629
7630
12.1k
    if (sourceCRSAuthName == targetCRSAuthName &&
7631
3.60k
        sourceCRSCode == targetCRSCode) {
7632
29
        return listTmp;
7633
29
    }
7634
7635
12.1k
    const auto CheckIfHasOperations = [this](const std::string &auth_name,
7636
22.2k
                                             const std::string &code) {
7637
22.2k
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7638
22.2k
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7639
22.2k
                        "(target_crs_auth_name = ? AND target_crs_code = ?) "
7640
22.2k
                        "LIMIT 1",
7641
22.2k
                        {auth_name, code, auth_name, code})
7642
22.2k
                     .empty());
7643
22.2k
    };
7644
7645
    // If the source or target CRS are not the source or target of an operation,
7646
    // do not run the next costly requests.
7647
12.1k
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7648
10.1k
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7649
3.78k
        return listTmp;
7650
3.78k
    }
7651
7652
8.34k
    const std::string sqlProlog(
7653
8.34k
        discardSuperseded
7654
8.34k
            ?
7655
7656
8.34k
            "SELECT v1.table_name as table1, "
7657
8.34k
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7658
8.34k
            "v1.accuracy AS accuracy1, "
7659
8.34k
            "v2.table_name as table2, "
7660
8.34k
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7661
8.34k
            "v2.accuracy as accuracy2, "
7662
8.34k
            "a1.south_lat AS south_lat1, "
7663
8.34k
            "a1.west_lon AS west_lon1, "
7664
8.34k
            "a1.north_lat AS north_lat1, "
7665
8.34k
            "a1.east_lon AS east_lon1, "
7666
8.34k
            "a2.south_lat AS south_lat2, "
7667
8.34k
            "a2.west_lon AS west_lon2, "
7668
8.34k
            "a2.north_lat AS north_lat2, "
7669
8.34k
            "a2.east_lon AS east_lon2, "
7670
8.34k
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7671
8.34k
            "ss1.replacement_code AS replacement_code1, "
7672
8.34k
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7673
8.34k
            "ss2.replacement_code AS replacement_code2 "
7674
8.34k
            "FROM coordinate_operation_view v1 "
7675
8.34k
            "JOIN coordinate_operation_view v2 "
7676
8.34k
            :
7677
7678
8.34k
            "SELECT v1.table_name as table1, "
7679
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7680
0
            "v1.accuracy AS accuracy1, "
7681
0
            "v2.table_name as table2, "
7682
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7683
0
            "v2.accuracy as accuracy2, "
7684
0
            "a1.south_lat AS south_lat1, "
7685
0
            "a1.west_lon AS west_lon1, "
7686
0
            "a1.north_lat AS north_lat1, "
7687
0
            "a1.east_lon AS east_lon1, "
7688
0
            "a2.south_lat AS south_lat2, "
7689
0
            "a2.west_lon AS west_lon2, "
7690
0
            "a2.north_lat AS north_lat2, "
7691
0
            "a2.east_lon AS east_lon2 "
7692
0
            "FROM coordinate_operation_view v1 "
7693
0
            "JOIN coordinate_operation_view v2 ");
7694
7695
8.34k
    const char *joinSupersession =
7696
8.34k
        "LEFT JOIN supersession ss1 ON "
7697
8.34k
        "ss1.superseded_table_name = v1.table_name AND "
7698
8.34k
        "ss1.superseded_auth_name = v1.auth_name AND "
7699
8.34k
        "ss1.superseded_code = v1.code AND "
7700
8.34k
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7701
8.34k
        "ss1.same_source_target_crs = 1 "
7702
8.34k
        "LEFT JOIN supersession ss2 ON "
7703
8.34k
        "ss2.superseded_table_name = v2.table_name AND "
7704
8.34k
        "ss2.superseded_auth_name = v2.auth_name AND "
7705
8.34k
        "ss2.superseded_code = v2.code AND "
7706
8.34k
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7707
8.34k
        "ss2.same_source_target_crs = 1 ";
7708
8.34k
    const std::string joinArea(
7709
8.34k
        (discardSuperseded ? std::string(joinSupersession) : std::string())
7710
8.34k
            .append("JOIN usage u1 ON "
7711
8.34k
                    "u1.object_table_name = v1.table_name AND "
7712
8.34k
                    "u1.object_auth_name = v1.auth_name AND "
7713
8.34k
                    "u1.object_code = v1.code "
7714
8.34k
                    "JOIN extent a1 "
7715
8.34k
                    "ON a1.auth_name = u1.extent_auth_name AND "
7716
8.34k
                    "a1.code = u1.extent_code "
7717
8.34k
                    "JOIN usage u2 ON "
7718
8.34k
                    "u2.object_table_name = v2.table_name AND "
7719
8.34k
                    "u2.object_auth_name = v2.auth_name AND "
7720
8.34k
                    "u2.object_code = v2.code "
7721
8.34k
                    "JOIN extent a2 "
7722
8.34k
                    "ON a2.auth_name = u2.extent_auth_name AND "
7723
8.34k
                    "a2.code = u2.extent_code "));
7724
8.34k
    const std::string orderBy(
7725
8.34k
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7726
8.34k
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7727
8.34k
        "accuracy1 + accuracy2");
7728
7729
    // Case (source->intermediate) and (intermediate->target)
7730
8.34k
    std::string sql(
7731
8.34k
        sqlProlog +
7732
8.34k
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7733
8.34k
        "AND v1.target_crs_code = v2.source_crs_code " +
7734
8.34k
        joinArea +
7735
8.34k
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7736
8.34k
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7737
8.34k
    std::string minDate;
7738
8.34k
    std::string criterionOnIntermediateCRS;
7739
7740
8.34k
    const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7741
8.34k
                               ->createCoordinateReferenceSystem(sourceCRSCode);
7742
8.34k
    const auto targetCRS = d->createFactory(targetCRSAuthName)
7743
8.34k
                               ->createCoordinateReferenceSystem(targetCRSCode);
7744
7745
8.34k
    const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7746
1.31k
                            starts_with(targetCRS->nameStr(), "ETRF");
7747
7748
8.34k
    const bool NAD83_CSRS_to_NAD83_CSRS =
7749
8.34k
        starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") &&
7750
2
        starts_with(targetCRS->nameStr(), "NAD83(CSRS)");
7751
7752
8.34k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7753
5.81k
        const auto &sourceGeogCRS =
7754
5.81k
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7755
5.81k
        const auto &targetGeogCRS =
7756
5.81k
            dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7757
5.81k
        if (sourceGeogCRS && targetGeogCRS) {
7758
5.81k
            const auto &sourceDatum = sourceGeogCRS->datum();
7759
5.81k
            const auto &targetDatum = targetGeogCRS->datum();
7760
5.81k
            if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7761
1.91k
                targetDatum && targetDatum->publicationDate().has_value()) {
7762
644
                const auto sourceDate(
7763
644
                    sourceDatum->publicationDate()->toString());
7764
644
                const auto targetDate(
7765
644
                    targetDatum->publicationDate()->toString());
7766
644
                minDate = std::min(sourceDate, targetDate);
7767
                // Check that the datum of the intermediateCRS has a publication
7768
                // date most recent that the one of the source and the target
7769
                // CRS Except when using the usual WGS84 pivot which happens to
7770
                // have a NULL publication date.
7771
644
                criterionOnIntermediateCRS =
7772
644
                    "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7773
644
                    "JOIN geodetic_datum y "
7774
644
                    "ON "
7775
644
                    "y.auth_name = x.datum_auth_name AND "
7776
644
                    "y.code = x.datum_code "
7777
644
                    "WHERE "
7778
644
                    "x.auth_name = v1.target_crs_auth_name AND "
7779
644
                    "x.code = v1.target_crs_code AND "
7780
644
                    "x.type IN ('geographic 2D', 'geographic 3D') AND "
7781
644
                    "(y.publication_date IS NULL OR "
7782
644
                    "(y.publication_date >= '" +
7783
644
                    minDate + "'))) ";
7784
644
            }
7785
5.81k
        }
7786
5.81k
        if (criterionOnIntermediateCRS.empty()) {
7787
5.17k
            criterionOnIntermediateCRS =
7788
5.17k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7789
5.17k
                "x.auth_name = v1.target_crs_auth_name AND "
7790
5.17k
                "x.code = v1.target_crs_code AND "
7791
5.17k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7792
5.17k
        }
7793
5.81k
        sql += criterionOnIntermediateCRS;
7794
5.81k
    }
7795
8.34k
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7796
8.34k
                               targetCRSAuthName, targetCRSCode};
7797
8.34k
    std::string additionalWhere(
7798
8.34k
        skipIntermediateExtentIntersection
7799
8.34k
            ? "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7800
8.34k
            : "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7801
8.34k
              "AND intersects_bbox(south_lat1, west_lon1, north_lat1, "
7802
8.34k
              "east_lon1, south_lat2, west_lon2, north_lat2, "
7803
8.34k
              "east_lon2) = 1 ");
7804
8.34k
    if (!allowedAuthorities.empty()) {
7805
6.71k
        additionalWhere += "AND v1.auth_name IN (";
7806
26.8k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7807
20.1k
            if (i > 0)
7808
13.4k
                additionalWhere += ',';
7809
20.1k
            additionalWhere += '?';
7810
20.1k
        }
7811
6.71k
        additionalWhere += ") AND v2.auth_name IN (";
7812
26.8k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7813
20.1k
            if (i > 0)
7814
13.4k
                additionalWhere += ',';
7815
20.1k
            additionalWhere += '?';
7816
20.1k
        }
7817
6.71k
        additionalWhere += ')';
7818
20.1k
        for (const auto &allowedAuthority : allowedAuthorities) {
7819
20.1k
            params.emplace_back(allowedAuthority);
7820
20.1k
        }
7821
20.1k
        for (const auto &allowedAuthority : allowedAuthorities) {
7822
20.1k
            params.emplace_back(allowedAuthority);
7823
20.1k
        }
7824
6.71k
    }
7825
8.34k
    if (d->hasAuthorityRestriction()) {
7826
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7827
0
        params.emplace_back(d->authority());
7828
0
        params.emplace_back(d->authority());
7829
0
    }
7830
16.6k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7831
16.6k
        if (extent) {
7832
8.20k
            const auto &geogExtent = extent->geographicElements();
7833
8.20k
            if (geogExtent.size() == 1) {
7834
8.20k
                auto bbox =
7835
8.20k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7836
8.20k
                        geogExtent[0].get());
7837
8.20k
                if (bbox) {
7838
8.20k
                    const double south_lat = bbox->southBoundLatitude();
7839
8.20k
                    const double west_lon = bbox->westBoundLongitude();
7840
8.20k
                    const double north_lat = bbox->northBoundLatitude();
7841
8.20k
                    const double east_lon = bbox->eastBoundLongitude();
7842
8.20k
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7843
5.60k
                        north_lat != 90.0 || east_lon != 180.0) {
7844
5.60k
                        additionalWhere +=
7845
5.60k
                            "AND intersects_bbox(south_lat1, "
7846
5.60k
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7847
5.60k
                            "intersects_bbox(south_lat2, west_lon2, "
7848
5.60k
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7849
5.60k
                        params.emplace_back(south_lat);
7850
5.60k
                        params.emplace_back(west_lon);
7851
5.60k
                        params.emplace_back(north_lat);
7852
5.60k
                        params.emplace_back(east_lon);
7853
5.60k
                        params.emplace_back(south_lat);
7854
5.60k
                        params.emplace_back(west_lon);
7855
5.60k
                        params.emplace_back(north_lat);
7856
5.60k
                        params.emplace_back(east_lon);
7857
5.60k
                    }
7858
8.20k
                }
7859
8.20k
            }
7860
8.20k
        }
7861
16.6k
    }
7862
7863
8.34k
    const auto buildIntermediateWhere =
7864
8.34k
        [&intermediateCRSAuthCodes](const std::string &first_field,
7865
33.3k
                                    const std::string &second_field) {
7866
33.3k
            if (intermediateCRSAuthCodes.empty()) {
7867
33.3k
                return std::string();
7868
33.3k
            }
7869
0
            std::string l_sql(" AND (");
7870
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7871
0
                if (i > 0) {
7872
0
                    l_sql += " OR";
7873
0
                }
7874
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7875
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7876
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7877
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7878
0
            }
7879
0
            l_sql += ')';
7880
0
            return l_sql;
7881
33.3k
        };
7882
7883
8.34k
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7884
8.34k
    for (const auto &pair : intermediateCRSAuthCodes) {
7885
0
        params.emplace_back(pair.first);
7886
0
        params.emplace_back(pair.second);
7887
0
        params.emplace_back(pair.first);
7888
0
        params.emplace_back(pair.second);
7889
0
    }
7890
8.34k
    auto res =
7891
8.34k
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7892
7893
33.3k
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7894
33.3k
        std::set<std::pair<std::string, std::string>> setTransf1;
7895
33.3k
        std::set<std::pair<std::string, std::string>> setTransf2;
7896
33.3k
        for (const auto &row : resultSet) {
7897
            // table1
7898
27.1k
            const auto &auth_name1 = row[1];
7899
27.1k
            const auto &code1 = row[2];
7900
            // accuracy1
7901
            // table2
7902
27.1k
            const auto &auth_name2 = row[5];
7903
27.1k
            const auto &code2 = row[6];
7904
27.1k
            setTransf1.insert(
7905
27.1k
                std::pair<std::string, std::string>(auth_name1, code1));
7906
27.1k
            setTransf2.insert(
7907
27.1k
                std::pair<std::string, std::string>(auth_name2, code2));
7908
27.1k
        }
7909
33.3k
        SQLResultSet filteredResultSet;
7910
33.3k
        for (const auto &row : resultSet) {
7911
27.1k
            const auto &replacement_auth_name1 = row[16];
7912
27.1k
            const auto &replacement_code1 = row[17];
7913
27.1k
            const auto &replacement_auth_name2 = row[18];
7914
27.1k
            const auto &replacement_code2 = row[19];
7915
27.1k
            if (!replacement_auth_name1.empty() &&
7916
23
                setTransf1.find(std::pair<std::string, std::string>(
7917
23
                    replacement_auth_name1, replacement_code1)) !=
7918
23
                    setTransf1.end()) {
7919
                // Skip transformations that are superseded by others that got
7920
                // returned in the result set.
7921
16
                continue;
7922
16
            }
7923
27.1k
            if (!replacement_auth_name2.empty() &&
7924
15
                setTransf2.find(std::pair<std::string, std::string>(
7925
15
                    replacement_auth_name2, replacement_code2)) !=
7926
15
                    setTransf2.end()) {
7927
                // Skip transformations that are superseded by others that got
7928
                // returned in the result set.
7929
11
                continue;
7930
11
            }
7931
27.1k
            filteredResultSet.emplace_back(row);
7932
27.1k
        }
7933
33.3k
        return filteredResultSet;
7934
33.3k
    };
7935
7936
8.34k
    if (discardSuperseded) {
7937
8.34k
        res = filterOutSuperseded(std::move(res));
7938
8.34k
    }
7939
7940
8.34k
    const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS,
7941
19.5k
                             &targetCRS](const crs::CRSPtr &intermediateCRS) {
7942
        // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or
7943
        // ITRF>2014
7944
19.5k
        if (ETRFtoETRF && intermediateCRS &&
7945
16
            starts_with(intermediateCRS->nameStr(), "ITRF")) {
7946
48
            const auto normalizeDate = [](int v) {
7947
48
                return (v >= 80 && v <= 99) ? v + 1900 : v;
7948
48
            };
7949
16
            const int srcDate = normalizeDate(
7950
16
                atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7951
16
            const int tgtDate = normalizeDate(
7952
16
                atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7953
16
            const int intermDate = normalizeDate(
7954
16
                atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7955
16
            if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7956
16
                if (intermDate < std::min(srcDate, tgtDate) ||
7957
8
                    intermDate > std::max(srcDate, tgtDate)) {
7958
8
                    return false;
7959
8
                }
7960
16
            }
7961
16
        }
7962
7963
        // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through
7964
        // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464
7965
19.5k
        if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS &&
7966
0
            (intermediateCRS->nameStr() == "NAD83" ||
7967
0
             intermediateCRS->nameStr() == "WGS 84")) {
7968
0
            return false;
7969
0
        }
7970
7971
19.5k
        return true;
7972
19.5k
    };
7973
7974
8.34k
    for (const auto &row : res) {
7975
18
        const auto &table1 = row[0];
7976
18
        const auto &auth_name1 = row[1];
7977
18
        const auto &code1 = row[2];
7978
        // const auto &accuracy1 = row[3];
7979
18
        const auto &table2 = row[4];
7980
18
        const auto &auth_name2 = row[5];
7981
18
        const auto &code2 = row[6];
7982
        // const auto &accuracy2 = row[7];
7983
18
        try {
7984
18
            auto op1 =
7985
18
                d->createFactory(auth_name1)
7986
18
                    ->createCoordinateOperation(
7987
18
                        code1, true, usePROJAlternativeGridNames, table1);
7988
18
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7989
18
                                   targetCRSAuthName, targetCRSCode)) {
7990
0
                continue;
7991
0
            }
7992
18
            if (!checkPivot(op1->targetCRS())) {
7993
0
                continue;
7994
0
            }
7995
18
            auto op2 =
7996
18
                d->createFactory(auth_name2)
7997
18
                    ->createCoordinateOperation(
7998
18
                        code2, true, usePROJAlternativeGridNames, table2);
7999
18
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8000
18
                                   targetCRSAuthName, targetCRSCode)) {
8001
0
                continue;
8002
0
            }
8003
8004
18
            listTmp.emplace_back(
8005
18
                operation::ConcatenatedOperation::createComputeMetadata(
8006
18
                    {std::move(op1), std::move(op2)}, false));
8007
18
        } catch (const std::exception &e) {
8008
            // Mostly for debugging purposes when using an inconsistent
8009
            // database
8010
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8011
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8012
0
            } else {
8013
0
                throw;
8014
0
            }
8015
0
        }
8016
18
    }
8017
8018
    // Case (source->intermediate) and (target->intermediate)
8019
8.34k
    sql = sqlProlog +
8020
8.34k
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
8021
8.34k
          "AND v1.target_crs_code = v2.target_crs_code " +
8022
8.34k
          joinArea +
8023
8.34k
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
8024
8.34k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8025
8.34k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8026
5.81k
        sql += criterionOnIntermediateCRS;
8027
5.81k
    }
8028
8.34k
    intermediateWhere = buildIntermediateWhere("target", "target");
8029
8.34k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8030
8.34k
    if (discardSuperseded) {
8031
8.34k
        res = filterOutSuperseded(std::move(res));
8032
8.34k
    }
8033
8034
26.9k
    for (const auto &row : res) {
8035
26.9k
        const auto &table1 = row[0];
8036
26.9k
        const auto &auth_name1 = row[1];
8037
26.9k
        const auto &code1 = row[2];
8038
        // const auto &accuracy1 = row[3];
8039
26.9k
        const auto &table2 = row[4];
8040
26.9k
        const auto &auth_name2 = row[5];
8041
26.9k
        const auto &code2 = row[6];
8042
        // const auto &accuracy2 = row[7];
8043
26.9k
        try {
8044
26.9k
            auto op1 =
8045
26.9k
                d->createFactory(auth_name1)
8046
26.9k
                    ->createCoordinateOperation(
8047
26.9k
                        code1, true, usePROJAlternativeGridNames, table1);
8048
26.9k
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8049
26.9k
                                   targetCRSAuthName, targetCRSCode)) {
8050
7.64k
                continue;
8051
7.64k
            }
8052
19.3k
            if (!checkPivot(op1->targetCRS())) {
8053
0
                continue;
8054
0
            }
8055
19.3k
            auto op2 =
8056
19.3k
                d->createFactory(auth_name2)
8057
19.3k
                    ->createCoordinateOperation(
8058
19.3k
                        code2, true, usePROJAlternativeGridNames, table2);
8059
19.3k
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8060
19.3k
                                   targetCRSAuthName, targetCRSCode)) {
8061
10.1k
                continue;
8062
10.1k
            }
8063
8064
9.12k
            listTmp.emplace_back(
8065
9.12k
                operation::ConcatenatedOperation::createComputeMetadata(
8066
9.12k
                    {std::move(op1), op2->inverse()}, false));
8067
9.12k
        } catch (const std::exception &e) {
8068
            // Mostly for debugging purposes when using an inconsistent
8069
            // database
8070
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8071
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8072
0
            } else {
8073
0
                throw;
8074
0
            }
8075
0
        }
8076
26.9k
    }
8077
8078
    // Case (intermediate->source) and (intermediate->target)
8079
8.34k
    sql = sqlProlog +
8080
8.34k
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
8081
8.34k
          "AND v1.source_crs_code = v2.source_crs_code " +
8082
8.34k
          joinArea +
8083
8.34k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8084
8.34k
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
8085
8.34k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8086
5.81k
        if (!minDate.empty()) {
8087
644
            criterionOnIntermediateCRS =
8088
644
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
8089
644
                "JOIN geodetic_datum y "
8090
644
                "ON "
8091
644
                "y.auth_name = x.datum_auth_name AND "
8092
644
                "y.code = x.datum_code "
8093
644
                "WHERE "
8094
644
                "x.auth_name = v1.source_crs_auth_name AND "
8095
644
                "x.code = v1.source_crs_code AND "
8096
644
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
8097
644
                "(y.publication_date IS NULL OR "
8098
644
                "(y.publication_date >= '" +
8099
644
                minDate + "'))) ";
8100
5.17k
        } else {
8101
5.17k
            criterionOnIntermediateCRS =
8102
5.17k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
8103
5.17k
                "x.auth_name = v1.source_crs_auth_name AND "
8104
5.17k
                "x.code = v1.source_crs_code AND "
8105
5.17k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
8106
5.17k
        }
8107
5.81k
        sql += criterionOnIntermediateCRS;
8108
5.81k
    }
8109
8.34k
    intermediateWhere = buildIntermediateWhere("source", "source");
8110
8.34k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8111
8.34k
    if (discardSuperseded) {
8112
8.34k
        res = filterOutSuperseded(std::move(res));
8113
8.34k
    }
8114
8.34k
    for (const auto &row : res) {
8115
153
        const auto &table1 = row[0];
8116
153
        const auto &auth_name1 = row[1];
8117
153
        const auto &code1 = row[2];
8118
        // const auto &accuracy1 = row[3];
8119
153
        const auto &table2 = row[4];
8120
153
        const auto &auth_name2 = row[5];
8121
153
        const auto &code2 = row[6];
8122
        // const auto &accuracy2 = row[7];
8123
153
        try {
8124
153
            auto op1 =
8125
153
                d->createFactory(auth_name1)
8126
153
                    ->createCoordinateOperation(
8127
153
                        code1, true, usePROJAlternativeGridNames, table1);
8128
153
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8129
153
                                   targetCRSAuthName, targetCRSCode)) {
8130
0
                continue;
8131
0
            }
8132
153
            if (!checkPivot(op1->sourceCRS())) {
8133
8
                continue;
8134
8
            }
8135
145
            auto op2 =
8136
145
                d->createFactory(auth_name2)
8137
145
                    ->createCoordinateOperation(
8138
145
                        code2, true, usePROJAlternativeGridNames, table2);
8139
145
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8140
145
                                   targetCRSAuthName, targetCRSCode)) {
8141
4
                continue;
8142
4
            }
8143
8144
141
            listTmp.emplace_back(
8145
141
                operation::ConcatenatedOperation::createComputeMetadata(
8146
141
                    {op1->inverse(), std::move(op2)}, false));
8147
141
        } catch (const std::exception &e) {
8148
            // Mostly for debugging purposes when using an inconsistent
8149
            // database
8150
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8151
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8152
0
            } else {
8153
0
                throw;
8154
0
            }
8155
0
        }
8156
153
    }
8157
8158
    // Case (intermediate->source) and (target->intermediate)
8159
8.34k
    sql = sqlProlog +
8160
8.34k
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
8161
8.34k
          "AND v1.source_crs_code = v2.target_crs_code " +
8162
8.34k
          joinArea +
8163
8.34k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8164
8.34k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8165
8.34k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8166
5.81k
        sql += criterionOnIntermediateCRS;
8167
5.81k
    }
8168
8.34k
    intermediateWhere = buildIntermediateWhere("source", "target");
8169
8.34k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8170
8.34k
    if (discardSuperseded) {
8171
8.34k
        res = filterOutSuperseded(std::move(res));
8172
8.34k
    }
8173
8.34k
    for (const auto &row : res) {
8174
21
        const auto &table1 = row[0];
8175
21
        const auto &auth_name1 = row[1];
8176
21
        const auto &code1 = row[2];
8177
        // const auto &accuracy1 = row[3];
8178
21
        const auto &table2 = row[4];
8179
21
        const auto &auth_name2 = row[5];
8180
21
        const auto &code2 = row[6];
8181
        // const auto &accuracy2 = row[7];
8182
21
        try {
8183
21
            auto op1 =
8184
21
                d->createFactory(auth_name1)
8185
21
                    ->createCoordinateOperation(
8186
21
                        code1, true, usePROJAlternativeGridNames, table1);
8187
21
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8188
21
                                   targetCRSAuthName, targetCRSCode)) {
8189
0
                continue;
8190
0
            }
8191
21
            if (!checkPivot(op1->sourceCRS())) {
8192
0
                continue;
8193
0
            }
8194
21
            auto op2 =
8195
21
                d->createFactory(auth_name2)
8196
21
                    ->createCoordinateOperation(
8197
21
                        code2, true, usePROJAlternativeGridNames, table2);
8198
21
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8199
21
                                   targetCRSAuthName, targetCRSCode)) {
8200
0
                continue;
8201
0
            }
8202
8203
21
            listTmp.emplace_back(
8204
21
                operation::ConcatenatedOperation::createComputeMetadata(
8205
21
                    {op1->inverse(), op2->inverse()}, false));
8206
21
        } catch (const std::exception &e) {
8207
            // Mostly for debugging purposes when using an inconsistent
8208
            // database
8209
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8210
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8211
0
            } else {
8212
0
                throw;
8213
0
            }
8214
0
        }
8215
21
    }
8216
8217
8.34k
    std::vector<operation::CoordinateOperationNNPtr> list;
8218
9.30k
    for (const auto &op : listTmp) {
8219
9.30k
        if (!discardIfMissingGrid ||
8220
9.30k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8221
892
            list.emplace_back(op);
8222
892
        }
8223
9.30k
    }
8224
8225
8.34k
    return list;
8226
8.34k
}
8227
8228
// ---------------------------------------------------------------------------
8229
8230
//! @cond Doxygen_Suppress
8231
8232
struct TrfmInfo {
8233
    std::string situation{};
8234
    std::string table_name{};
8235
    std::string auth_name{};
8236
    std::string code{};
8237
    std::string name{};
8238
    double west = 0;
8239
    double south = 0;
8240
    double east = 0;
8241
    double north = 0;
8242
};
8243
8244
// ---------------------------------------------------------------------------
8245
8246
std::vector<operation::CoordinateOperationNNPtr>
8247
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
8248
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
8249
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
8250
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
8251
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
8252
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
8253
    const std::vector<std::string> &allowedAuthorities,
8254
    const metadata::ExtentPtr &intersectingExtent1,
8255
    const metadata::ExtentPtr &intersectingExtent2,
8256
327
    bool skipIntermediateExtentIntersection) const {
8257
8258
327
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
8259
8260
327
    if (sourceCRSAuthName == targetCRSAuthName &&
8261
207
        sourceCRSCode == targetCRSCode) {
8262
0
        return listTmp;
8263
0
    }
8264
327
    const auto sourceGeodCRS =
8265
327
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
8266
327
    const auto targetGeodCRS =
8267
327
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
8268
327
    if (!sourceGeodCRS || !targetGeodCRS) {
8269
0
        return listTmp;
8270
0
    }
8271
8272
327
    const bool NAD83_CSRS_to_NAD83_CSRS =
8273
327
        starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") &&
8274
0
        starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)");
8275
8276
327
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
8277
327
                                                const std::string &crsAuthName,
8278
654
                                                const std::string &crsCode) {
8279
        // Find all geodetic CRS that share the same datum as the CRS
8280
654
        SQLResultSet listCRS;
8281
8282
654
        const common::IdentifiedObject *obj = crs->datum().get();
8283
654
        if (obj == nullptr)
8284
154
            obj = crs->datumEnsemble().get();
8285
654
        assert(obj != nullptr);
8286
654
        const auto &ids = obj->identifiers();
8287
654
        std::string datumAuthName;
8288
654
        std::string datumCode;
8289
654
        if (!ids.empty()) {
8290
654
            const auto &id = ids.front();
8291
654
            datumAuthName = *(id->codeSpace());
8292
654
            datumCode = id->code();
8293
654
        } else {
8294
0
            const auto res =
8295
0
                d->run("SELECT datum_auth_name, datum_code FROM "
8296
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
8297
0
                       {crsAuthName, crsCode});
8298
0
            if (res.size() != 1) {
8299
0
                return listCRS;
8300
0
            }
8301
0
            const auto &row = res.front();
8302
0
            datumAuthName = row[0];
8303
0
            datumCode = row[1];
8304
0
        }
8305
8306
654
        listCRS =
8307
654
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
8308
654
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
8309
654
                   {datumAuthName, datumCode});
8310
654
        if (listCRS.empty()) {
8311
            // Can happen if the CRS is deprecated
8312
13
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
8313
13
        }
8314
654
        return listCRS;
8315
654
    };
8316
8317
327
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
8318
327
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
8319
327
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
8320
327
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
8321
327
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
8322
        // would happen only if we had CRS objects in the database without a
8323
        // link to a datum.
8324
0
        return listTmp;
8325
0
    }
8326
8327
327
    ListOfParams params;
8328
327
    const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS,
8329
327
                               &allowedAuthorities, &params, &listSourceCRS,
8330
327
                               &listTargetCRS](bool isSourceCRS,
8331
1.30k
                                               bool selectOnTarget) {
8332
1.30k
        std::string situation;
8333
1.30k
        if (isSourceCRS)
8334
654
            situation = "src";
8335
654
        else
8336
654
            situation = "tgt";
8337
1.30k
        if (selectOnTarget)
8338
654
            situation += "_is_tgt";
8339
654
        else
8340
654
            situation += "_is_src";
8341
1.30k
        const std::string prefix1(selectOnTarget ? "source" : "target");
8342
1.30k
        const std::string prefix2(selectOnTarget ? "target" : "source");
8343
1.30k
        std::string sql("SELECT '");
8344
1.30k
        sql += situation;
8345
1.30k
        sql += "' as situation, v.table_name, v.auth_name, "
8346
1.30k
               "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
8347
1.30k
               "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
8348
1.30k
               "FROM coordinate_operation_view v "
8349
1.30k
               "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
8350
1.30k
        sql += prefix1;
8351
1.30k
        sql += "_crs_auth_name AND gcrs.code = ";
8352
1.30k
        sql += prefix1;
8353
1.30k
        sql += "_crs_code "
8354
8355
1.30k
               "LEFT JOIN usage u ON "
8356
1.30k
               "u.object_table_name = v.table_name AND "
8357
1.30k
               "u.object_auth_name = v.auth_name AND "
8358
1.30k
               "u.object_code = v.code "
8359
1.30k
               "LEFT JOIN extent a "
8360
1.30k
               "ON a.auth_name = u.extent_auth_name AND "
8361
1.30k
               "a.code = u.extent_code "
8362
1.30k
               "WHERE v.deprecated = 0 AND (";
8363
8364
1.30k
        std::string cond;
8365
8366
1.30k
        const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
8367
6.90k
        for (const auto &row : list) {
8368
6.90k
            if (!cond.empty())
8369
5.59k
                cond += " OR ";
8370
6.90k
            cond += '(';
8371
6.90k
            cond += prefix2;
8372
6.90k
            cond += "_crs_auth_name = ? AND ";
8373
6.90k
            cond += prefix2;
8374
6.90k
            cond += "_crs_code = ?)";
8375
6.90k
            params.emplace_back(row[0]);
8376
6.90k
            params.emplace_back(row[1]);
8377
6.90k
        }
8378
8379
1.30k
        sql += cond;
8380
1.30k
        sql += ") ";
8381
8382
1.30k
        if (!allowedAuthorities.empty()) {
8383
1.17k
            sql += "AND v.auth_name IN (";
8384
4.68k
            for (size_t i = 0; i < allowedAuthorities.size(); i++) {
8385
3.51k
                if (i > 0)
8386
2.34k
                    sql += ',';
8387
3.51k
                sql += '?';
8388
3.51k
            }
8389
1.17k
            sql += ") ";
8390
3.51k
            for (const auto &allowedAuthority : allowedAuthorities) {
8391
3.51k
                params.emplace_back(allowedAuthority);
8392
3.51k
            }
8393
1.17k
        }
8394
1.30k
        if (d->hasAuthorityRestriction()) {
8395
0
            sql += "AND v.auth_name = ? ";
8396
0
            params.emplace_back(d->authority());
8397
0
        }
8398
1.30k
        if (NAD83_CSRS_to_NAD83_CSRS) {
8399
            // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go
8400
            // through NAD83 generic. Cf
8401
            // https://github.com/OSGeo/PROJ/issues/4464
8402
0
            sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') ";
8403
0
        }
8404
8405
1.30k
        return sql;
8406
1.30k
    };
8407
8408
327
    std::string sql(BuildSQLPart(true, true));
8409
327
    sql += "UNION ALL ";
8410
327
    sql += BuildSQLPart(false, true);
8411
327
    sql += "UNION ALL ";
8412
327
    sql += BuildSQLPart(true, false);
8413
327
    sql += "UNION ALL ";
8414
327
    sql += BuildSQLPart(false, false);
8415
    // fprintf(stderr, "sql : %s\n", sql.c_str());
8416
8417
    // Find all operations that have as source/target CRS a CRS that
8418
    // share the same datum as the source or targetCRS
8419
327
    const auto res = d->run(sql, params);
8420
8421
327
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
8422
327
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
8423
8424
251k
    for (const auto &row : res) {
8425
251k
        try {
8426
251k
            TrfmInfo trfm;
8427
251k
            trfm.situation = row[0];
8428
251k
            trfm.table_name = row[1];
8429
251k
            trfm.auth_name = row[2];
8430
251k
            trfm.code = row[3];
8431
251k
            trfm.name = row[4];
8432
251k
            const auto &datum_auth_name = row[5];
8433
251k
            const auto &datum_code = row[6];
8434
251k
            trfm.west = c_locale_stod(row[7]);
8435
251k
            trfm.south = c_locale_stod(row[8]);
8436
251k
            trfm.east = c_locale_stod(row[9]);
8437
251k
            trfm.north = c_locale_stod(row[10]);
8438
251k
            const std::string key =
8439
251k
                std::string(datum_auth_name).append(":").append(datum_code);
8440
251k
            if (trfm.situation == "src_is_tgt" ||
8441
22.0k
                trfm.situation == "src_is_src")
8442
238k
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
8443
12.5k
            else
8444
12.5k
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
8445
251k
        } catch (const std::exception &) {
8446
0
        }
8447
251k
    }
8448
8449
327
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
8450
654
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
8451
654
        if (extent) {
8452
331
            const auto &geogExtent = extent->geographicElements();
8453
331
            if (geogExtent.size() == 1) {
8454
331
                auto bbox =
8455
331
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
8456
331
                        geogExtent[0].get());
8457
331
                if (bbox) {
8458
331
                    const double south_lat = bbox->southBoundLatitude();
8459
331
                    const double west_lon = bbox->westBoundLongitude();
8460
331
                    const double north_lat = bbox->northBoundLatitude();
8461
331
                    const double east_lon = bbox->eastBoundLongitude();
8462
331
                    if (south_lat != -90.0 || west_lon != -180.0 ||
8463
249
                        north_lat != 90.0 || east_lon != 180.0) {
8464
249
                        extraBbox.emplace_back(bbox);
8465
249
                    }
8466
331
                }
8467
331
            }
8468
331
        }
8469
654
    }
8470
8471
327
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
8472
327
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
8473
327
    std::map<std::string, TrfmInfo> setOfTransformations;
8474
8475
12.7k
    const auto MakeKey = [](const TrfmInfo &trfm) {
8476
12.7k
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
8477
12.7k
    };
8478
8479
    // Find transformations that share a pivot datum, and do bbox filtering
8480
89.8k
    for (const auto &kvSource : mapIntermDatumOfSource) {
8481
89.8k
        const auto &listTrmfSource = kvSource.second;
8482
89.8k
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
8483
89.8k
        if (iter == mapIntermDatumOfTarget.end())
8484
87.5k
            continue;
8485
8486
2.23k
        const auto &listTrfmTarget = iter->second;
8487
5.01k
        for (const auto &trfmSource : listTrmfSource) {
8488
5.01k
            auto bbox1 = metadata::GeographicBoundingBox::create(
8489
5.01k
                trfmSource.west, trfmSource.south, trfmSource.east,
8490
5.01k
                trfmSource.north);
8491
5.01k
            bool okBbox1 = true;
8492
5.01k
            for (const auto bbox : extraBbox)
8493
2.93k
                okBbox1 &= bbox->intersects(bbox1);
8494
5.01k
            if (!okBbox1)
8495
801
                continue;
8496
8497
4.21k
            const std::string key1 = MakeKey(trfmSource);
8498
8499
26.4k
            for (const auto &trfmTarget : listTrfmTarget) {
8500
26.4k
                auto bbox2 = metadata::GeographicBoundingBox::create(
8501
26.4k
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
8502
26.4k
                    trfmTarget.north);
8503
26.4k
                if (!skipIntermediateExtentIntersection &&
8504
26.4k
                    !bbox1->intersects(bbox2))
8505
23.6k
                    continue;
8506
2.84k
                bool okBbox2 = true;
8507
2.84k
                for (const auto bbox : extraBbox)
8508
2.28k
                    okBbox2 &= bbox->intersects(bbox2);
8509
2.84k
                if (!okBbox2)
8510
0
                    continue;
8511
8512
2.84k
                operation::CoordinateOperationPtr op1;
8513
2.84k
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
8514
2.38k
                    auto op1NN = d->createFactory(trfmSource.auth_name)
8515
2.38k
                                     ->createCoordinateOperation(
8516
2.38k
                                         trfmSource.code, true,
8517
2.38k
                                         usePROJAlternativeGridNames,
8518
2.38k
                                         trfmSource.table_name);
8519
2.38k
                    op1 = op1NN.as_nullable();
8520
2.38k
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
8521
2.38k
                                           sourceCRSCode, targetCRSAuthName,
8522
2.38k
                                           targetCRSCode)) {
8523
2
                        op1.reset();
8524
2
                    }
8525
2.38k
                    oMapTrfmKeyToOp[key1] = op1;
8526
2.38k
                } else {
8527
456
                    op1 = oMapTrfmKeyToOp[key1];
8528
456
                }
8529
2.84k
                if (op1 == nullptr)
8530
2
                    continue;
8531
8532
2.84k
                const std::string key2 = MakeKey(trfmTarget);
8533
8534
2.84k
                operation::CoordinateOperationPtr op2;
8535
2.84k
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
8536
2.29k
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
8537
2.29k
                                     ->createCoordinateOperation(
8538
2.29k
                                         trfmTarget.code, true,
8539
2.29k
                                         usePROJAlternativeGridNames,
8540
2.29k
                                         trfmTarget.table_name);
8541
2.29k
                    op2 = op2NN.as_nullable();
8542
2.29k
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
8543
2.29k
                                           sourceCRSCode, targetCRSAuthName,
8544
2.29k
                                           targetCRSCode)) {
8545
3
                        op2.reset();
8546
3
                    }
8547
2.29k
                    oMapTrfmKeyToOp[key2] = op2;
8548
2.29k
                } else {
8549
548
                    op2 = oMapTrfmKeyToOp[key2];
8550
548
                }
8551
2.84k
                if (op2 == nullptr)
8552
4
                    continue;
8553
8554
2.83k
                candidates.emplace_back(
8555
2.83k
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
8556
2.83k
                setOfTransformations[key1] = trfmSource;
8557
2.83k
                setOfTransformations[key2] = trfmTarget;
8558
2.83k
            }
8559
4.21k
        }
8560
2.23k
    }
8561
8562
327
    std::set<std::string> setSuperseded;
8563
327
    if (discardSuperseded && !setOfTransformations.empty()) {
8564
140
        std::string findSupersededSql(
8565
140
            "SELECT superseded_table_name, "
8566
140
            "superseded_auth_name, superseded_code, "
8567
140
            "replacement_auth_name, replacement_code "
8568
140
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
8569
140
        bool findSupersededFirstWhere = true;
8570
140
        ListOfParams findSupersededParams;
8571
8572
140
        const auto keyMapSupersession = [](const std::string &table_name,
8573
140
                                           const std::string &auth_name,
8574
4.68k
                                           const std::string &code) {
8575
4.68k
            return table_name + auth_name + code;
8576
4.68k
        };
8577
8578
140
        std::set<std::pair<std::string, std::string>> setTransf;
8579
4.67k
        for (const auto &kv : setOfTransformations) {
8580
4.67k
            const auto &table = kv.second.table_name;
8581
4.67k
            const auto &auth_name = kv.second.auth_name;
8582
4.67k
            const auto &code = kv.second.code;
8583
8584
4.67k
            if (!findSupersededFirstWhere)
8585
4.53k
                findSupersededSql += " OR ";
8586
4.67k
            findSupersededFirstWhere = false;
8587
4.67k
            findSupersededSql +=
8588
4.67k
                "(superseded_table_name = ? AND replacement_table_name = "
8589
4.67k
                "superseded_table_name AND superseded_auth_name = ? AND "
8590
4.67k
                "superseded_code = ?)";
8591
4.67k
            findSupersededParams.push_back(table);
8592
4.67k
            findSupersededParams.push_back(auth_name);
8593
4.67k
            findSupersededParams.push_back(code);
8594
8595
4.67k
            setTransf.insert(
8596
4.67k
                std::pair<std::string, std::string>(auth_name, code));
8597
4.67k
        }
8598
140
        findSupersededSql += ')';
8599
8600
140
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
8601
140
            mapSupersession;
8602
8603
140
        const auto resSuperseded =
8604
140
            d->run(findSupersededSql, findSupersededParams);
8605
140
        for (const auto &row : resSuperseded) {
8606
14
            const auto &superseded_table_name = row[0];
8607
14
            const auto &superseded_auth_name = row[1];
8608
14
            const auto &superseded_code = row[2];
8609
14
            const auto &replacement_auth_name = row[3];
8610
14
            const auto &replacement_code = row[4];
8611
14
            mapSupersession[keyMapSupersession(superseded_table_name,
8612
14
                                               superseded_auth_name,
8613
14
                                               superseded_code)]
8614
14
                .push_back(std::pair<std::string, std::string>(
8615
14
                    replacement_auth_name, replacement_code));
8616
14
        }
8617
8618
4.67k
        for (const auto &kv : setOfTransformations) {
8619
4.67k
            const auto &table = kv.second.table_name;
8620
4.67k
            const auto &auth_name = kv.second.auth_name;
8621
4.67k
            const auto &code = kv.second.code;
8622
8623
4.67k
            const auto iter = mapSupersession.find(
8624
4.67k
                keyMapSupersession(table, auth_name, code));
8625
4.67k
            if (iter != mapSupersession.end()) {
8626
14
                bool foundReplacement = false;
8627
14
                for (const auto &replacement : iter->second) {
8628
14
                    const auto &replacement_auth_name = replacement.first;
8629
14
                    const auto &replacement_code = replacement.second;
8630
14
                    if (setTransf.find(std::pair<std::string, std::string>(
8631
14
                            replacement_auth_name, replacement_code)) !=
8632
14
                        setTransf.end()) {
8633
                        // Skip transformations that are superseded by others
8634
                        // that got
8635
                        // returned in the result set.
8636
14
                        foundReplacement = true;
8637
14
                        break;
8638
14
                    }
8639
14
                }
8640
14
                if (foundReplacement) {
8641
14
                    setSuperseded.insert(kv.first);
8642
14
                }
8643
14
            }
8644
4.67k
        }
8645
140
    }
8646
8647
327
    std::string sourceDatumPubDate;
8648
327
    const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context());
8649
327
    if (sourceDatum->publicationDate().has_value()) {
8650
114
        sourceDatumPubDate = sourceDatum->publicationDate()->toString();
8651
114
    }
8652
8653
327
    std::string targetDatumPubDate;
8654
327
    const auto targetDatum = targetGeodCRS->datumNonNull(d->context());
8655
327
    if (targetDatum->publicationDate().has_value()) {
8656
295
        targetDatumPubDate = targetDatum->publicationDate()->toString();
8657
295
    }
8658
8659
327
    const std::string mostAncientDatumPubDate =
8660
327
        (!targetDatumPubDate.empty() &&
8661
295
         (sourceDatumPubDate.empty() ||
8662
107
          targetDatumPubDate < sourceDatumPubDate))
8663
327
            ? targetDatumPubDate
8664
327
            : sourceDatumPubDate;
8665
8666
327
    auto opFactory = operation::CoordinateOperationFactory::create();
8667
2.83k
    for (const auto &pair : candidates) {
8668
2.83k
        const auto &trfmSource = pair.first;
8669
2.83k
        const auto &trfmTarget = pair.second;
8670
2.83k
        const std::string key1 = MakeKey(trfmSource);
8671
2.83k
        const std::string key2 = MakeKey(trfmTarget);
8672
2.83k
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8673
2.83k
            setSuperseded.find(key2) != setSuperseded.end()) {
8674
26
            continue;
8675
26
        }
8676
2.81k
        auto op1 = oMapTrfmKeyToOp[key1];
8677
2.81k
        auto op2 = oMapTrfmKeyToOp[key2];
8678
2.81k
        auto op1NN = NN_NO_CHECK(op1);
8679
2.81k
        auto op2NN = NN_NO_CHECK(op2);
8680
2.81k
        if (trfmSource.situation == "src_is_tgt")
8681
2.62k
            op1NN = op1NN->inverse();
8682
2.81k
        if (trfmTarget.situation == "tgt_is_src")
8683
1.11k
            op2NN = op2NN->inverse();
8684
8685
2.81k
        const auto &op1Source = op1NN->sourceCRS();
8686
2.81k
        const auto &op1Target = op1NN->targetCRS();
8687
2.81k
        const auto &op2Source = op2NN->sourceCRS();
8688
2.81k
        const auto &op2Target = op2NN->targetCRS();
8689
2.81k
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8690
0
            continue;
8691
0
        }
8692
8693
        // Skip operations using a datum that is older than the source or
8694
        // target datum (e.g to avoid ED50 to WGS84 to go through NTF)
8695
2.81k
        if (!mostAncientDatumPubDate.empty()) {
8696
2.79k
            const auto isOlderCRS = [this, &mostAncientDatumPubDate](
8697
11.1k
                                        const crs::CRSPtr &crs) {
8698
11.1k
                const auto geogCRS =
8699
11.1k
                    dynamic_cast<const crs::GeodeticCRS *>(crs.get());
8700
11.1k
                if (geogCRS) {
8701
11.1k
                    const auto datum = geogCRS->datumNonNull(d->context());
8702
                    // Hum, theoretically we'd want to check
8703
                    // datum->publicationDate()->toString() <
8704
                    // mostAncientDatumPubDate but that would exclude doing
8705
                    // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008,
8706
                    // since IG05/12 Intermediate CRS has been published later
8707
                    // than ITRF2008. So use a cut of date for ancient vs
8708
                    // "modern" era.
8709
11.1k
                    constexpr const char *CUT_OFF_DATE = "1900-01-01";
8710
11.1k
                    if (datum->publicationDate().has_value() &&
8711
5.42k
                        datum->publicationDate()->toString() < CUT_OFF_DATE &&
8712
0
                        mostAncientDatumPubDate > CUT_OFF_DATE) {
8713
0
                        return true;
8714
0
                    }
8715
11.1k
                }
8716
11.1k
                return false;
8717
11.1k
            };
8718
8719
2.79k
            if (isOlderCRS(op1Source) || isOlderCRS(op1Target) ||
8720
2.79k
                isOlderCRS(op2Source) || isOlderCRS(op2Target))
8721
0
                continue;
8722
2.79k
        }
8723
8724
2.81k
        std::vector<operation::CoordinateOperationNNPtr> steps;
8725
8726
2.81k
        if (!sourceCRS->isEquivalentTo(
8727
2.81k
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8728
682
            auto opFirst =
8729
682
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8730
682
            assert(opFirst);
8731
682
            steps.emplace_back(NN_NO_CHECK(opFirst));
8732
682
        }
8733
8734
2.81k
        steps.emplace_back(op1NN);
8735
8736
2.81k
        if (!op1Target->isEquivalentTo(
8737
2.81k
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8738
2.52k
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8739
2.52k
                                                       NN_NO_CHECK(op2Source));
8740
2.52k
            assert(opMiddle);
8741
2.52k
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8742
2.52k
        }
8743
8744
2.81k
        steps.emplace_back(op2NN);
8745
8746
2.81k
        if (!op2Target->isEquivalentTo(
8747
2.81k
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8748
2.10k
            auto opLast =
8749
2.10k
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8750
2.10k
            assert(opLast);
8751
2.10k
            steps.emplace_back(NN_NO_CHECK(opLast));
8752
2.10k
        }
8753
8754
2.81k
        listTmp.emplace_back(
8755
2.81k
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8756
2.81k
                                                                    false));
8757
2.81k
    }
8758
8759
327
    std::vector<operation::CoordinateOperationNNPtr> list;
8760
2.81k
    for (const auto &op : listTmp) {
8761
2.81k
        if (!discardIfMissingGrid ||
8762
2.81k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8763
2.04k
            list.emplace_back(op);
8764
2.04k
        }
8765
2.81k
    }
8766
8767
327
    return list;
8768
327
}
8769
8770
//! @endcond
8771
8772
// ---------------------------------------------------------------------------
8773
8774
/** \brief Returns the authority name associated to this factory.
8775
 * @return name.
8776
 */
8777
109k
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8778
109k
    return d->authority();
8779
109k
}
8780
8781
// ---------------------------------------------------------------------------
8782
8783
/** \brief Returns the set of authority codes of the given object type.
8784
 *
8785
 * @param type Object type.
8786
 * @param allowDeprecated whether we should return deprecated objects as well.
8787
 * @return the set of authority codes for spatial reference objects of the given
8788
 * type
8789
 * @throw FactoryException in case of error.
8790
 */
8791
std::set<std::string>
8792
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8793
0
                                    bool allowDeprecated) const {
8794
0
    std::string sql;
8795
0
    switch (type) {
8796
0
    case ObjectType::PRIME_MERIDIAN:
8797
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8798
0
        break;
8799
0
    case ObjectType::ELLIPSOID:
8800
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8801
0
        break;
8802
0
    case ObjectType::DATUM:
8803
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8804
0
              "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND ";
8805
0
        break;
8806
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8807
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8808
0
        break;
8809
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8810
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8811
0
              "frame_reference_epoch IS NOT NULL AND ";
8812
0
        break;
8813
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8814
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8815
0
        break;
8816
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8817
0
        sql = "SELECT code FROM vertical_datum WHERE "
8818
0
              "frame_reference_epoch IS NOT NULL AND ";
8819
0
        break;
8820
0
    case ObjectType::ENGINEERING_DATUM:
8821
0
        sql = "SELECT code FROM engineering_datum WHERE ";
8822
0
        break;
8823
0
    case ObjectType::CRS:
8824
0
        sql = "SELECT code FROM crs_view WHERE ";
8825
0
        break;
8826
0
    case ObjectType::GEODETIC_CRS:
8827
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8828
0
        break;
8829
0
    case ObjectType::GEOCENTRIC_CRS:
8830
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8831
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8832
0
        break;
8833
0
    case ObjectType::GEOGRAPHIC_CRS:
8834
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8835
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8836
0
        break;
8837
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8838
0
        sql =
8839
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8840
0
            " AND ";
8841
0
        break;
8842
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8843
0
        sql =
8844
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8845
0
            " AND ";
8846
0
        break;
8847
0
    case ObjectType::VERTICAL_CRS:
8848
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8849
0
        break;
8850
0
    case ObjectType::PROJECTED_CRS:
8851
0
        sql = "SELECT code FROM projected_crs WHERE ";
8852
0
        break;
8853
0
    case ObjectType::COMPOUND_CRS:
8854
0
        sql = "SELECT code FROM compound_crs WHERE ";
8855
0
        break;
8856
0
    case ObjectType::ENGINEERING_CRS:
8857
0
        sql = "SELECT code FROM engineering_crs WHERE ";
8858
0
        break;
8859
0
    case ObjectType::DERIVED_PROJECTED_CRS:
8860
0
        sql = "SELECT code FROM derived_projected_crs WHERE ";
8861
0
        break;
8862
0
    case ObjectType::COORDINATE_OPERATION:
8863
0
        sql =
8864
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8865
0
        break;
8866
0
    case ObjectType::CONVERSION:
8867
0
        sql = "SELECT code FROM conversion WHERE ";
8868
0
        break;
8869
0
    case ObjectType::TRANSFORMATION:
8870
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8871
0
              "'concatenated_operation' AND ";
8872
0
        break;
8873
0
    case ObjectType::CONCATENATED_OPERATION:
8874
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8875
0
        break;
8876
0
    case ObjectType::DATUM_ENSEMBLE:
8877
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8878
0
              "('geodetic_datum', 'vertical_datum') AND "
8879
0
              "type = 'ensemble' AND ";
8880
0
        break;
8881
0
    }
8882
8883
0
    sql += "auth_name = ?";
8884
0
    if (!allowDeprecated) {
8885
0
        sql += " AND deprecated = 0";
8886
0
    }
8887
8888
0
    auto res = d->run(sql, {d->authority()});
8889
0
    std::set<std::string> set;
8890
0
    for (const auto &row : res) {
8891
0
        set.insert(row[0]);
8892
0
    }
8893
0
    return set;
8894
0
}
8895
8896
// ---------------------------------------------------------------------------
8897
8898
/** \brief Gets a description of the object corresponding to a code.
8899
 *
8900
 * \note In case of several objects of different types with the same code,
8901
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8902
 * will be selected.
8903
 *
8904
 * @param code Object code allocated by authority. (e.g. "4326")
8905
 * @return description.
8906
 * @throw NoSuchAuthorityCodeException if there is no matching object.
8907
 * @throw FactoryException in case of other errors.
8908
 */
8909
std::string
8910
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8911
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8912
0
               "AND code = ? ORDER BY table_name";
8913
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8914
0
    if (sqlRes.empty()) {
8915
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8916
0
                                           code);
8917
0
    }
8918
0
    std::string text;
8919
0
    for (const auto &row : sqlRes) {
8920
0
        const auto &tableName = row[1];
8921
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8922
0
            tableName == "vertical_crs" || tableName == "compound_crs" ||
8923
0
            tableName == "engineering_crs" ||
8924
0
            tableName == "derived_projected_crs") {
8925
0
            return row[0];
8926
0
        } else if (text.empty()) {
8927
0
            text = row[0];
8928
0
        }
8929
0
    }
8930
0
    return text;
8931
0
}
8932
8933
// ---------------------------------------------------------------------------
8934
8935
/** \brief Return a list of information on CRS objects
8936
 *
8937
 * This is functionally equivalent of listing the codes from an authority,
8938
 * instantiating
8939
 * a CRS object for each of them and getting the information from this CRS
8940
 * object, but this implementation has much less overhead.
8941
 *
8942
 * @throw FactoryException in case of error.
8943
 */
8944
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8945
8946
0
    const auto getSqlArea = [](const char *table_name) {
8947
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8948
0
        sql += table_name;
8949
0
        sql += "' AND "
8950
0
               "u.object_auth_name = c.auth_name AND "
8951
0
               "u.object_code = c.code "
8952
0
               "LEFT JOIN extent a "
8953
0
               "ON a.auth_name = u.extent_auth_name AND "
8954
0
               "a.code = u.extent_code ";
8955
0
        return sql;
8956
0
    };
8957
8958
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8959
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8960
0
        sql += crs_alias;
8961
0
        sql += ".datum_auth_name AND gd.code = ";
8962
0
        sql += crs_alias;
8963
0
        sql += ".datum_code "
8964
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8965
0
               "AND e.code = gd.ellipsoid_code "
8966
0
               "LEFT JOIN celestial_body cb ON "
8967
0
               "cb.auth_name = e.celestial_body_auth_name "
8968
0
               "AND cb.code = e.celestial_body_code ";
8969
0
        return sql;
8970
0
    };
8971
8972
0
    std::string sql = "SELECT * FROM ("
8973
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8974
0
                      "c.deprecated, "
8975
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8976
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8977
0
    sql += getSqlArea("geodetic_crs");
8978
0
    sql += getJoinCelestialBody("c");
8979
0
    ListOfParams params;
8980
0
    if (d->hasAuthorityRestriction()) {
8981
0
        sql += "WHERE c.auth_name = ? ";
8982
0
        params.emplace_back(d->authority());
8983
0
    }
8984
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8985
0
           "c.deprecated, "
8986
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8987
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8988
0
           "projected_crs c "
8989
0
           "LEFT JOIN conversion_table conv ON "
8990
0
           "c.conversion_auth_name = conv.auth_name AND "
8991
0
           "c.conversion_code = conv.code "
8992
0
           "LEFT JOIN conversion_method cm ON "
8993
0
           "conv.method_auth_name = cm.auth_name AND "
8994
0
           "conv.method_code = cm.code "
8995
0
           "LEFT JOIN geodetic_crs gcrs ON "
8996
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
8997
0
           "AND gcrs.code = c.geodetic_crs_code ";
8998
0
    sql += getSqlArea("projected_crs");
8999
0
    sql += getJoinCelestialBody("gcrs");
9000
0
    if (d->hasAuthorityRestriction()) {
9001
0
        sql += "WHERE c.auth_name = ? ";
9002
0
        params.emplace_back(d->authority());
9003
0
    }
9004
    // FIXME: we can't handle non-EARTH vertical CRS for now
9005
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
9006
0
           "c.deprecated, "
9007
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9008
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
9009
0
    sql += getSqlArea("vertical_crs");
9010
0
    if (d->hasAuthorityRestriction()) {
9011
0
        sql += "WHERE c.auth_name = ? ";
9012
0
        params.emplace_back(d->authority());
9013
0
    }
9014
    // FIXME: we can't handle non-EARTH compound CRS for now
9015
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
9016
0
           "c.deprecated, "
9017
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9018
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
9019
0
    sql += getSqlArea("compound_crs");
9020
0
    if (d->hasAuthorityRestriction()) {
9021
0
        sql += "WHERE c.auth_name = ? ";
9022
0
        params.emplace_back(d->authority());
9023
0
    }
9024
    // FIXME: we can't handle non-EARTH compound CRS for now
9025
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', "
9026
0
           "c.deprecated, "
9027
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9028
0
           "a.description, NULL, 'Earth' FROM engineering_crs c ";
9029
0
    sql += getSqlArea("engineering_crs");
9030
0
    if (d->hasAuthorityRestriction()) {
9031
0
        sql += "WHERE c.auth_name = ? ";
9032
0
        params.emplace_back(d->authority());
9033
0
    }
9034
    // FIXME: we can't handle non-EARTH compound CRS for now
9035
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'derived projected', "
9036
0
           "c.deprecated, "
9037
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
9038
0
           "a.description, NULL, 'Earth' FROM derived_projected_crs c ";
9039
0
    sql += getSqlArea("derived_projected_crs");
9040
0
    if (d->hasAuthorityRestriction()) {
9041
0
        sql += "WHERE c.auth_name = ? ";
9042
0
        params.emplace_back(d->authority());
9043
0
    }
9044
0
    sql += ") r ORDER BY auth_name, code";
9045
0
    auto sqlRes = d->run(sql, params);
9046
0
    std::list<AuthorityFactory::CRSInfo> res;
9047
0
    for (const auto &row : sqlRes) {
9048
0
        AuthorityFactory::CRSInfo info;
9049
0
        info.authName = row[0];
9050
0
        info.code = row[1];
9051
0
        info.name = row[2];
9052
0
        const auto &type = row[3];
9053
0
        if (type == CRS_SUBTYPE_GEOG_2D) {
9054
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
9055
0
        } else if (type == CRS_SUBTYPE_GEOG_3D) {
9056
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
9057
0
        } else if (type == CRS_SUBTYPE_GEOCENTRIC) {
9058
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
9059
0
        } else if (type == CRS_SUBTYPE_OTHER) {
9060
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
9061
0
        } else if (type == CRS_SUBTYPE_PROJECTED) {
9062
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
9063
0
        } else if (type == CRS_SUBTYPE_VERTICAL) {
9064
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
9065
0
        } else if (type == CRS_SUBTYPE_COMPOUND) {
9066
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
9067
0
        } else if (type == CRS_SUBTYPE_ENGINEERING) {
9068
0
            info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS;
9069
0
        } else if (type == CRS_SUBTYPE_DERIVED_PROJECTED) {
9070
0
            info.type = AuthorityFactory::ObjectType::DERIVED_PROJECTED_CRS;
9071
0
        }
9072
0
        info.deprecated = row[4] == "1";
9073
0
        if (row[5].empty()) {
9074
0
            info.bbox_valid = false;
9075
0
        } else {
9076
0
            info.bbox_valid = true;
9077
0
            info.west_lon_degree = c_locale_stod(row[5]);
9078
0
            info.south_lat_degree = c_locale_stod(row[6]);
9079
0
            info.east_lon_degree = c_locale_stod(row[7]);
9080
0
            info.north_lat_degree = c_locale_stod(row[8]);
9081
0
        }
9082
0
        info.areaName = row[9];
9083
0
        info.projectionMethodName = row[10];
9084
0
        info.celestialBodyName = row[11];
9085
0
        res.emplace_back(info);
9086
0
    }
9087
0
    return res;
9088
0
}
9089
9090
// ---------------------------------------------------------------------------
9091
9092
//! @cond Doxygen_Suppress
9093
AuthorityFactory::UnitInfo::UnitInfo()
9094
0
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
9095
0
      deprecated{} {}
9096
//! @endcond
9097
9098
// ---------------------------------------------------------------------------
9099
9100
//! @cond Doxygen_Suppress
9101
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
9102
//! @endcond
9103
9104
// ---------------------------------------------------------------------------
9105
9106
/** \brief Return the list of units.
9107
 * @throw FactoryException in case of error.
9108
 *
9109
 * @since 7.1
9110
 */
9111
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
9112
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
9113
0
                      "proj_short_name, deprecated FROM unit_of_measure";
9114
0
    ListOfParams params;
9115
0
    if (d->hasAuthorityRestriction()) {
9116
0
        sql += " WHERE auth_name = ?";
9117
0
        params.emplace_back(d->authority());
9118
0
    }
9119
0
    sql += " ORDER BY auth_name, code";
9120
9121
0
    auto sqlRes = d->run(sql, params);
9122
0
    std::list<AuthorityFactory::UnitInfo> res;
9123
0
    for (const auto &row : sqlRes) {
9124
0
        AuthorityFactory::UnitInfo info;
9125
0
        info.authName = row[0];
9126
0
        info.code = row[1];
9127
0
        info.name = row[2];
9128
0
        const std::string &raw_category(row[3]);
9129
0
        if (raw_category == "length") {
9130
0
            info.category = info.name.find(" per ") != std::string::npos
9131
0
                                ? "linear_per_time"
9132
0
                                : "linear";
9133
0
        } else if (raw_category == "angle") {
9134
0
            info.category = info.name.find(" per ") != std::string::npos
9135
0
                                ? "angular_per_time"
9136
0
                                : "angular";
9137
0
        } else if (raw_category == "scale") {
9138
0
            info.category =
9139
0
                info.name.find(" per year") != std::string::npos ||
9140
0
                        info.name.find(" per second") != std::string::npos
9141
0
                    ? "scale_per_time"
9142
0
                    : "scale";
9143
0
        } else {
9144
0
            info.category = raw_category;
9145
0
        }
9146
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
9147
0
        info.projShortName = row[5];
9148
0
        info.deprecated = row[6] == "1";
9149
0
        res.emplace_back(info);
9150
0
    }
9151
0
    return res;
9152
0
}
9153
9154
// ---------------------------------------------------------------------------
9155
9156
/** \brief Return the list of celestial bodies.
9157
 * @throw FactoryException in case of error.
9158
 *
9159
 * @since 8.1
9160
 */
9161
std::list<AuthorityFactory::CelestialBodyInfo>
9162
0
AuthorityFactory::getCelestialBodyList() const {
9163
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
9164
0
    ListOfParams params;
9165
0
    if (d->hasAuthorityRestriction()) {
9166
0
        sql += " WHERE auth_name = ?";
9167
0
        params.emplace_back(d->authority());
9168
0
    }
9169
0
    sql += " ORDER BY auth_name, name";
9170
9171
0
    auto sqlRes = d->run(sql, params);
9172
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
9173
0
    for (const auto &row : sqlRes) {
9174
0
        AuthorityFactory::CelestialBodyInfo info;
9175
0
        info.authName = row[0];
9176
0
        info.name = row[1];
9177
0
        res.emplace_back(info);
9178
0
    }
9179
0
    return res;
9180
0
}
9181
9182
// ---------------------------------------------------------------------------
9183
9184
/** \brief Gets the official name from a possibly alias name.
9185
 *
9186
 * @param aliasedName Alias name.
9187
 * @param tableName Table name/category. Can help in case of ambiguities.
9188
 * Or empty otherwise.
9189
 * @param source Source of the alias. Can help in case of ambiguities.
9190
 * Or empty otherwise.
9191
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
9192
 * the alt_name column of the alias_name table should be done with using
9193
 * metadata::Identifier::isEquivalentName() rather than strict string
9194
 * comparison;
9195
 * @param outTableName Table name in which the official name has been found.
9196
 * @param outAuthName Authority name of the official name that has been found.
9197
 * @param outCode Code of the official name that has been found.
9198
 * @return official name (or empty if not found).
9199
 * @throw FactoryException in case of error.
9200
 */
9201
std::string AuthorityFactory::getOfficialNameFromAlias(
9202
    const std::string &aliasedName, const std::string &tableName,
9203
    const std::string &source, bool tryEquivalentNameSpelling,
9204
    std::string &outTableName, std::string &outAuthName,
9205
3.35k
    std::string &outCode) const {
9206
9207
3.35k
    if (tryEquivalentNameSpelling) {
9208
0
        std::string sql(
9209
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
9210
0
        ListOfParams params;
9211
0
        if (!tableName.empty()) {
9212
0
            sql += " WHERE table_name = ?";
9213
0
            params.push_back(tableName);
9214
0
        }
9215
0
        if (!source.empty()) {
9216
0
            if (!tableName.empty()) {
9217
0
                sql += " AND ";
9218
0
            } else {
9219
0
                sql += " WHERE ";
9220
0
            }
9221
0
            sql += "source = ?";
9222
0
            params.push_back(source);
9223
0
        }
9224
0
        auto res = d->run(sql, params);
9225
0
        if (res.empty()) {
9226
0
            return std::string();
9227
0
        }
9228
0
        for (const auto &row : res) {
9229
0
            const auto &alt_name = row[3];
9230
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
9231
0
                                                       aliasedName.c_str())) {
9232
0
                outTableName = row[0];
9233
0
                outAuthName = row[1];
9234
0
                outCode = row[2];
9235
0
                sql = "SELECT name FROM \"";
9236
0
                sql += replaceAll(outTableName, "\"", "\"\"");
9237
0
                sql += "\" WHERE auth_name = ? AND code = ?";
9238
0
                res = d->run(sql, {outAuthName, outCode});
9239
0
                if (res.empty()) { // shouldn't happen normally
9240
0
                    return std::string();
9241
0
                }
9242
0
                return res.front()[0];
9243
0
            }
9244
0
        }
9245
0
        return std::string();
9246
3.35k
    } else {
9247
3.35k
        std::string sql(
9248
3.35k
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
9249
3.35k
            "alt_name = ?");
9250
3.35k
        ListOfParams params{aliasedName};
9251
3.35k
        if (!tableName.empty()) {
9252
3.35k
            sql += " AND table_name = ?";
9253
3.35k
            params.push_back(tableName);
9254
3.35k
        }
9255
3.35k
        if (!source.empty()) {
9256
3.35k
            if (source == "ESRI") {
9257
3.35k
                sql += " AND source IN ('ESRI', 'ESRI_OLD')";
9258
3.35k
            } else {
9259
0
                sql += " AND source = ?";
9260
0
                params.push_back(source);
9261
0
            }
9262
3.35k
        }
9263
3.35k
        auto res = d->run(sql, params);
9264
3.35k
        if (res.empty()) {
9265
2.20k
            return std::string();
9266
2.20k
        }
9267
9268
1.15k
        params.clear();
9269
1.15k
        sql.clear();
9270
1.15k
        bool first = true;
9271
1.15k
        for (const auto &row : res) {
9272
1.15k
            if (!first)
9273
3
                sql += " UNION ALL ";
9274
1.15k
            first = false;
9275
1.15k
            outTableName = row[0];
9276
1.15k
            outAuthName = row[1];
9277
1.15k
            outCode = row[2];
9278
1.15k
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
9279
1.15k
                   "FROM \"";
9280
1.15k
            sql += replaceAll(outTableName, "\"", "\"\"");
9281
1.15k
            sql += "\" WHERE auth_name = ? AND code = ?";
9282
1.15k
            params.emplace_back(outTableName);
9283
1.15k
            params.emplace_back(outAuthName);
9284
1.15k
            params.emplace_back(outCode);
9285
1.15k
        }
9286
1.15k
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
9287
1.15k
              ") x ORDER BY deprecated LIMIT 1";
9288
1.15k
        res = d->run(sql, params);
9289
1.15k
        if (res.empty()) { // shouldn't happen normally
9290
0
            return std::string();
9291
0
        }
9292
1.15k
        const auto &row = res.front();
9293
1.15k
        outTableName = row[1];
9294
1.15k
        outAuthName = row[2];
9295
1.15k
        outCode = row[3];
9296
1.15k
        return row[0];
9297
1.15k
    }
9298
3.35k
}
9299
9300
// ---------------------------------------------------------------------------
9301
9302
/** \brief Return a list of objects, identified by their name
9303
 *
9304
 * @param searchedName Searched name. Must be at least 2 character long.
9305
 * @param allowedObjectTypes List of object types into which to search. If
9306
 * empty, all object types will be searched.
9307
 * @param approximateMatch Whether approximate name identification is allowed.
9308
 * @param limitResultCount Maximum number of results to return.
9309
 * Or 0 for unlimited.
9310
 * @return list of matched objects.
9311
 * @throw FactoryException in case of error.
9312
 */
9313
std::list<common::IdentifiedObjectNNPtr>
9314
AuthorityFactory::createObjectsFromName(
9315
    const std::string &searchedName,
9316
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9317
60.3k
    size_t limitResultCount) const {
9318
60.3k
    std::list<common::IdentifiedObjectNNPtr> res;
9319
60.3k
    const auto resTmp(createObjectsFromNameEx(
9320
60.3k
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
9321
60.3k
    for (const auto &pair : resTmp) {
9322
31.2k
        res.emplace_back(pair.first);
9323
31.2k
    }
9324
60.3k
    return res;
9325
60.3k
}
9326
9327
// ---------------------------------------------------------------------------
9328
9329
//! @cond Doxygen_Suppress
9330
9331
/** \brief Return a list of objects, identifier by their name, with the name
9332
 * on which the match occurred.
9333
 *
9334
 * The name on which the match occurred might be different from the object name,
9335
 * if the match has been done on an alias name of that object.
9336
 *
9337
 * @param searchedName Searched name. Must be at least 2 character long.
9338
 * @param allowedObjectTypes List of object types into which to search. If
9339
 * empty, all object types will be searched.
9340
 * @param approximateMatch Whether approximate name identification is allowed.
9341
 * @param limitResultCount Maximum number of results to return.
9342
 * Or 0 for unlimited.
9343
 * @param useAliases Whether querying the alias_name table is allowed
9344
 * @return list of matched objects.
9345
 * @throw FactoryException in case of error.
9346
 */
9347
std::list<AuthorityFactory::PairObjectName>
9348
AuthorityFactory::createObjectsFromNameEx(
9349
    const std::string &searchedName,
9350
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9351
60.3k
    size_t limitResultCount, bool useAliases) const {
9352
60.3k
    std::string searchedNameWithoutDeprecated(searchedName);
9353
60.3k
    bool deprecated = false;
9354
60.3k
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
9355
0
        deprecated = true;
9356
0
        searchedNameWithoutDeprecated.resize(
9357
0
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
9358
0
    }
9359
9360
60.3k
    const std::string canonicalizedSearchedName(
9361
60.3k
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
9362
60.3k
    if (canonicalizedSearchedName.size() <= 1) {
9363
1.98k
        return {};
9364
1.98k
    }
9365
9366
58.3k
    std::string sql(
9367
58.3k
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
9368
58.3k
        "FROM (");
9369
9370
58.3k
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
9371
58.3k
                                             &searchedName]() {
9372
58.3k
        typedef std::pair<std::string, std::string> TableType;
9373
58.3k
        std::list<TableType> res;
9374
        // Hide ESRI D_ vertical datums
9375
58.3k
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
9376
58.3k
        if (allowedObjectTypes.empty()) {
9377
0
            for (const auto &tableName :
9378
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
9379
0
                  "vertical_datum", "engineering_datum", "geodetic_crs",
9380
0
                  "projected_crs", "derived_projected_crs", "vertical_crs",
9381
0
                  "compound_crs", "engineering_crs", "conversion",
9382
0
                  "helmert_transformation", "grid_transformation",
9383
0
                  "other_transformation", "concatenated_operation"}) {
9384
0
                if (!(startsWithDUnderscore &&
9385
0
                      strcmp(tableName, "vertical_datum") == 0)) {
9386
0
                    res.emplace_back(TableType(tableName, std::string()));
9387
0
                }
9388
0
            }
9389
58.3k
        } else {
9390
67.2k
            for (const auto type : allowedObjectTypes) {
9391
67.2k
                switch (type) {
9392
0
                case ObjectType::PRIME_MERIDIAN:
9393
0
                    res.emplace_back(
9394
0
                        TableType("prime_meridian", std::string()));
9395
0
                    break;
9396
3.85k
                case ObjectType::ELLIPSOID:
9397
3.85k
                    res.emplace_back(TableType("ellipsoid", std::string()));
9398
3.85k
                    break;
9399
2.95k
                case ObjectType::DATUM:
9400
2.95k
                    res.emplace_back(
9401
2.95k
                        TableType("geodetic_datum", std::string()));
9402
2.95k
                    if (!startsWithDUnderscore) {
9403
2.93k
                        res.emplace_back(
9404
2.93k
                            TableType("vertical_datum", std::string()));
9405
2.93k
                        res.emplace_back(
9406
2.93k
                            TableType("engineering_datum", std::string()));
9407
2.93k
                    }
9408
2.95k
                    break;
9409
14.9k
                case ObjectType::GEODETIC_REFERENCE_FRAME:
9410
14.9k
                    res.emplace_back(
9411
14.9k
                        TableType("geodetic_datum", std::string()));
9412
14.9k
                    break;
9413
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
9414
0
                    res.emplace_back(
9415
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
9416
0
                    break;
9417
1.84k
                case ObjectType::VERTICAL_REFERENCE_FRAME:
9418
1.84k
                    res.emplace_back(
9419
1.84k
                        TableType("vertical_datum", std::string()));
9420
1.84k
                    break;
9421
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
9422
0
                    res.emplace_back(
9423
0
                        TableType("vertical_datum", "frame_reference_epoch"));
9424
0
                    break;
9425
0
                case ObjectType::ENGINEERING_DATUM:
9426
0
                    res.emplace_back(
9427
0
                        TableType("engineering_datum", std::string()));
9428
0
                    break;
9429
3.80k
                case ObjectType::CRS:
9430
3.80k
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9431
3.80k
                    res.emplace_back(TableType("projected_crs", std::string()));
9432
3.80k
                    res.emplace_back(TableType("vertical_crs", std::string()));
9433
3.80k
                    res.emplace_back(TableType("compound_crs", std::string()));
9434
3.80k
                    res.emplace_back(
9435
3.80k
                        TableType("engineering_crs", std::string()));
9436
3.80k
                    res.emplace_back(
9437
3.80k
                        TableType("derived_projected_crs", std::string()));
9438
3.80k
                    break;
9439
0
                case ObjectType::GEODETIC_CRS:
9440
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9441
0
                    break;
9442
0
                case ObjectType::GEOCENTRIC_CRS:
9443
0
                    res.emplace_back(
9444
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOCENTRIC));
9445
0
                    break;
9446
0
                case ObjectType::GEOGRAPHIC_CRS:
9447
0
                    res.emplace_back(
9448
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_2D));
9449
0
                    res.emplace_back(
9450
0
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_3D));
9451
0
                    break;
9452
8.54k
                case ObjectType::GEOGRAPHIC_2D_CRS:
9453
8.54k
                    res.emplace_back(
9454
8.54k
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_2D));
9455
8.54k
                    break;
9456
24.7k
                case ObjectType::GEOGRAPHIC_3D_CRS:
9457
24.7k
                    res.emplace_back(
9458
24.7k
                        TableType("geodetic_crs", CRS_SUBTYPE_GEOG_3D));
9459
24.7k
                    break;
9460
202
                case ObjectType::PROJECTED_CRS:
9461
202
                    res.emplace_back(TableType("projected_crs", std::string()));
9462
202
                    break;
9463
0
                case ObjectType::DERIVED_PROJECTED_CRS:
9464
0
                    res.emplace_back(
9465
0
                        TableType("derived_projected_crs", std::string()));
9466
0
                    break;
9467
263
                case ObjectType::VERTICAL_CRS:
9468
263
                    res.emplace_back(TableType("vertical_crs", std::string()));
9469
263
                    break;
9470
140
                case ObjectType::COMPOUND_CRS:
9471
140
                    res.emplace_back(TableType("compound_crs", std::string()));
9472
140
                    break;
9473
0
                case ObjectType::ENGINEERING_CRS:
9474
0
                    res.emplace_back(
9475
0
                        TableType("engineering_crs", std::string()));
9476
0
                    break;
9477
2.95k
                case ObjectType::COORDINATE_OPERATION:
9478
2.95k
                    res.emplace_back(TableType("conversion", std::string()));
9479
2.95k
                    res.emplace_back(
9480
2.95k
                        TableType("helmert_transformation", std::string()));
9481
2.95k
                    res.emplace_back(
9482
2.95k
                        TableType("grid_transformation", std::string()));
9483
2.95k
                    res.emplace_back(
9484
2.95k
                        TableType("other_transformation", std::string()));
9485
2.95k
                    res.emplace_back(
9486
2.95k
                        TableType("concatenated_operation", std::string()));
9487
2.95k
                    break;
9488
0
                case ObjectType::CONVERSION:
9489
0
                    res.emplace_back(TableType("conversion", std::string()));
9490
0
                    break;
9491
0
                case ObjectType::TRANSFORMATION:
9492
0
                    res.emplace_back(
9493
0
                        TableType("helmert_transformation", std::string()));
9494
0
                    res.emplace_back(
9495
0
                        TableType("grid_transformation", std::string()));
9496
0
                    res.emplace_back(
9497
0
                        TableType("other_transformation", std::string()));
9498
0
                    break;
9499
0
                case ObjectType::CONCATENATED_OPERATION:
9500
0
                    res.emplace_back(
9501
0
                        TableType("concatenated_operation", std::string()));
9502
0
                    break;
9503
2.96k
                case ObjectType::DATUM_ENSEMBLE:
9504
2.96k
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
9505
2.96k
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
9506
2.96k
                    break;
9507
67.2k
                }
9508
67.2k
            }
9509
58.3k
        }
9510
58.3k
        return res;
9511
58.3k
    };
9512
9513
58.3k
    bool datumEnsembleAllowed = false;
9514
58.3k
    if (allowedObjectTypes.empty()) {
9515
0
        datumEnsembleAllowed = true;
9516
58.3k
    } else {
9517
64.2k
        for (const auto type : allowedObjectTypes) {
9518
64.2k
            if (type == ObjectType::DATUM_ENSEMBLE) {
9519
2.96k
                datumEnsembleAllowed = true;
9520
2.96k
                break;
9521
2.96k
            }
9522
64.2k
        }
9523
58.3k
    }
9524
9525
58.3k
    const auto listTableNameType = getTableAndTypeConstraints();
9526
58.3k
    bool first = true;
9527
58.3k
    ListOfParams params;
9528
106k
    for (const auto &tableNameTypePair : listTableNameType) {
9529
106k
        if (!first) {
9530
48.5k
            sql += " UNION ";
9531
48.5k
        }
9532
106k
        first = false;
9533
106k
        sql += "SELECT '";
9534
106k
        sql += tableNameTypePair.first;
9535
106k
        sql += "' AS table_name, auth_name, code, name, deprecated, "
9536
106k
               "0 AS is_alias FROM ";
9537
106k
        sql += tableNameTypePair.first;
9538
106k
        sql += " WHERE 1 = 1 ";
9539
106k
        if (!tableNameTypePair.second.empty()) {
9540
39.2k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9541
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
9542
39.2k
            } else if (tableNameTypePair.second == "ensemble") {
9543
5.92k
                sql += "AND ensemble_accuracy IS NOT NULL ";
9544
33.3k
            } else {
9545
33.3k
                sql += "AND type = '";
9546
33.3k
                sql += tableNameTypePair.second;
9547
33.3k
                sql += "' ";
9548
33.3k
            }
9549
39.2k
        }
9550
106k
        if (deprecated) {
9551
0
            sql += "AND deprecated = 1 ";
9552
0
        }
9553
106k
        if (!approximateMatch) {
9554
85.8k
            sql += "AND name = ? COLLATE NOCASE ";
9555
85.8k
            params.push_back(searchedNameWithoutDeprecated);
9556
85.8k
        }
9557
106k
        if (d->hasAuthorityRestriction()) {
9558
25.7k
            sql += "AND auth_name = ? ";
9559
25.7k
            params.emplace_back(d->authority());
9560
25.7k
        }
9561
9562
106k
        if (useAliases) {
9563
106k
            sql += " UNION SELECT '";
9564
106k
            sql += tableNameTypePair.first;
9565
106k
            sql += "' AS table_name, "
9566
106k
                   "ov.auth_name AS auth_name, "
9567
106k
                   "ov.code AS code, a.alt_name AS name, "
9568
106k
                   "ov.deprecated AS deprecated, 1 as is_alias FROM ";
9569
106k
            sql += tableNameTypePair.first;
9570
106k
            sql += " ov "
9571
106k
                   "JOIN alias_name a ON "
9572
106k
                   "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
9573
106k
                   "a.source != 'EPSG_OLD' AND a.table_name = '";
9574
106k
            sql += tableNameTypePair.first;
9575
106k
            sql += "' ";
9576
106k
            if (!tableNameTypePair.second.empty()) {
9577
39.2k
                if (tableNameTypePair.second == "frame_reference_epoch") {
9578
0
                    sql += "AND ov.frame_reference_epoch IS NOT NULL ";
9579
39.2k
                } else if (tableNameTypePair.second == "ensemble") {
9580
5.92k
                    sql += "AND ov.ensemble_accuracy IS NOT NULL ";
9581
33.3k
                } else {
9582
33.3k
                    sql += "AND ov.type = '";
9583
33.3k
                    sql += tableNameTypePair.second;
9584
33.3k
                    sql += "' ";
9585
33.3k
                }
9586
39.2k
            }
9587
106k
            if (deprecated) {
9588
0
                sql += "AND ov.deprecated = 1 ";
9589
0
            }
9590
106k
            if (!approximateMatch) {
9591
85.8k
                sql += "AND a.alt_name = ? COLLATE NOCASE ";
9592
85.8k
                params.push_back(searchedNameWithoutDeprecated);
9593
85.8k
            }
9594
106k
            if (d->hasAuthorityRestriction()) {
9595
25.7k
                sql += "AND ov.auth_name = ? ";
9596
25.7k
                params.emplace_back(d->authority());
9597
25.7k
            }
9598
106k
        }
9599
106k
    }
9600
9601
58.3k
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
9602
58.3k
    if (limitResultCount > 0 &&
9603
32.6k
        limitResultCount <
9604
32.6k
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
9605
32.6k
        !approximateMatch) {
9606
29.0k
        sql += " LIMIT ";
9607
29.0k
        sql += toString(static_cast<int>(limitResultCount));
9608
29.0k
    }
9609
9610
58.3k
    std::list<PairObjectName> res;
9611
58.3k
    std::set<std::pair<std::string, std::string>> setIdentified;
9612
9613
    // Querying geodetic datum is a super hot path when importing from WKT1
9614
    // so cache results.
9615
58.3k
    if (allowedObjectTypes.size() == 1 &&
9616
55.4k
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
9617
14.9k
        approximateMatch && d->authority().empty()) {
9618
151
        auto &mapCanonicalizeGRFName =
9619
151
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
9620
151
        if (mapCanonicalizeGRFName.empty()) {
9621
93
            auto sqlRes = d->run(sql, params);
9622
232k
            for (const auto &row : sqlRes) {
9623
232k
                const auto &name = row[3];
9624
232k
                const auto &deprecatedStr = row[4];
9625
232k
                const auto canonicalizedName(
9626
232k
                    metadata::Identifier::canonicalizeName(name));
9627
232k
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
9628
232k
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
9629
223k
                    v.push_back(row);
9630
223k
                }
9631
232k
            }
9632
93
        }
9633
151
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
9634
151
        if (iter != mapCanonicalizeGRFName.end()) {
9635
16
            const auto &listOfRow = iter->second;
9636
16
            for (const auto &row : listOfRow) {
9637
16
                const auto &auth_name = row[1];
9638
16
                const auto &code = row[2];
9639
16
                auto key = std::pair<std::string, std::string>(auth_name, code);
9640
16
                if (setIdentified.find(key) != setIdentified.end()) {
9641
0
                    continue;
9642
0
                }
9643
16
                setIdentified.insert(std::move(key));
9644
16
                auto factory = d->createFactory(auth_name);
9645
16
                const auto &name = row[3];
9646
16
                res.emplace_back(
9647
16
                    PairObjectName(factory->createGeodeticDatum(code), name));
9648
16
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9649
16
                    break;
9650
16
                }
9651
16
            }
9652
135
        } else {
9653
194k
            for (const auto &pair : mapCanonicalizeGRFName) {
9654
194k
                const auto &listOfRow = pair.second;
9655
212k
                for (const auto &row : listOfRow) {
9656
212k
                    const auto &name = row[3];
9657
212k
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9658
212k
                                 std::string::npos;
9659
212k
                    if (!match) {
9660
212k
                        const auto &canonicalizedName(pair.first);
9661
212k
                        match = ci_find(canonicalizedName,
9662
212k
                                        canonicalizedSearchedName) !=
9663
212k
                                std::string::npos;
9664
212k
                    }
9665
212k
                    if (!match) {
9666
212k
                        continue;
9667
212k
                    }
9668
9669
61
                    const auto &auth_name = row[1];
9670
61
                    const auto &code = row[2];
9671
61
                    auto key =
9672
61
                        std::pair<std::string, std::string>(auth_name, code);
9673
61
                    if (setIdentified.find(key) != setIdentified.end()) {
9674
0
                        continue;
9675
0
                    }
9676
61
                    setIdentified.insert(std::move(key));
9677
61
                    auto factory = d->createFactory(auth_name);
9678
61
                    res.emplace_back(PairObjectName(
9679
61
                        factory->createGeodeticDatum(code), name));
9680
61
                    if (limitResultCount > 0 &&
9681
61
                        res.size() == limitResultCount) {
9682
61
                        break;
9683
61
                    }
9684
61
                }
9685
194k
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9686
61
                    break;
9687
61
                }
9688
194k
            }
9689
135
        }
9690
58.2k
    } else {
9691
58.2k
        auto sqlRes = d->run(sql, params);
9692
58.2k
        bool isFirst = true;
9693
58.2k
        bool firstIsDeprecated = false;
9694
58.2k
        size_t countExactMatch = 0;
9695
58.2k
        size_t countExactMatchOnAlias = 0;
9696
58.2k
        std::size_t hashCodeFirstMatch = 0;
9697
41.0M
        for (const auto &row : sqlRes) {
9698
41.0M
            const auto &name = row[3];
9699
41.0M
            if (approximateMatch) {
9700
41.0M
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9701
41.0M
                             std::string::npos;
9702
41.0M
                if (!match) {
9703
41.0M
                    const auto canonicalizedName(
9704
41.0M
                        metadata::Identifier::canonicalizeName(name));
9705
41.0M
                    match =
9706
41.0M
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
9707
41.0M
                        std::string::npos;
9708
41.0M
                }
9709
41.0M
                if (!match) {
9710
41.0M
                    continue;
9711
41.0M
                }
9712
41.0M
            }
9713
31.9k
            const auto &table_name = row[0];
9714
31.9k
            const auto &auth_name = row[1];
9715
31.9k
            const auto &code = row[2];
9716
31.9k
            auto key = std::pair<std::string, std::string>(auth_name, code);
9717
31.9k
            if (setIdentified.find(key) != setIdentified.end()) {
9718
707
                continue;
9719
707
            }
9720
31.2k
            setIdentified.insert(std::move(key));
9721
31.2k
            const auto &deprecatedStr = row[4];
9722
31.2k
            if (isFirst) {
9723
21.5k
                firstIsDeprecated = deprecatedStr == "1";
9724
21.5k
                isFirst = false;
9725
21.5k
            }
9726
31.2k
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
9727
81
                break;
9728
81
            }
9729
31.2k
            auto factory = d->createFactory(auth_name);
9730
31.2k
            auto getObject = [&factory, datumEnsembleAllowed](
9731
31.2k
                                 const std::string &l_table_name,
9732
31.2k
                                 const std::string &l_code)
9733
31.2k
                -> common::IdentifiedObjectNNPtr {
9734
31.2k
                if (l_table_name == "prime_meridian") {
9735
0
                    return factory->createPrimeMeridian(l_code);
9736
31.2k
                } else if (l_table_name == "ellipsoid") {
9737
264
                    return factory->createEllipsoid(l_code);
9738
30.9k
                } else if (l_table_name == "geodetic_datum") {
9739
377
                    if (datumEnsembleAllowed) {
9740
377
                        datum::GeodeticReferenceFramePtr datum;
9741
377
                        datum::DatumEnsemblePtr datumEnsemble;
9742
377
                        constexpr bool turnEnsembleAsDatum = false;
9743
377
                        factory->createGeodeticDatumOrEnsemble(
9744
377
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9745
377
                        if (datum) {
9746
377
                            return NN_NO_CHECK(datum);
9747
377
                        }
9748
377
                        assert(datumEnsemble);
9749
0
                        return NN_NO_CHECK(datumEnsemble);
9750
377
                    }
9751
0
                    return factory->createGeodeticDatum(l_code);
9752
30.5k
                } else if (l_table_name == "vertical_datum") {
9753
168
                    if (datumEnsembleAllowed) {
9754
168
                        datum::VerticalReferenceFramePtr datum;
9755
168
                        datum::DatumEnsemblePtr datumEnsemble;
9756
168
                        constexpr bool turnEnsembleAsDatum = false;
9757
168
                        factory->createVerticalDatumOrEnsemble(
9758
168
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9759
168
                        if (datum) {
9760
163
                            return NN_NO_CHECK(datum);
9761
163
                        }
9762
168
                        assert(datumEnsemble);
9763
5
                        return NN_NO_CHECK(datumEnsemble);
9764
168
                    }
9765
0
                    return factory->createVerticalDatum(l_code);
9766
30.3k
                } else if (l_table_name == "engineering_datum") {
9767
7
                    return factory->createEngineeringDatum(l_code);
9768
30.3k
                } else if (l_table_name == "geodetic_crs") {
9769
24.4k
                    return factory->createGeodeticCRS(l_code);
9770
24.4k
                } else if (l_table_name == "projected_crs") {
9771
3.42k
                    return factory->createProjectedCRS(l_code);
9772
3.42k
                } else if (l_table_name == "derived_projected_crs") {
9773
0
                    return factory->createDerivedProjectedCRS(l_code);
9774
2.48k
                } else if (l_table_name == "vertical_crs") {
9775
877
                    return factory->createVerticalCRS(l_code);
9776
1.60k
                } else if (l_table_name == "compound_crs") {
9777
127
                    return factory->createCompoundCRS(l_code);
9778
1.48k
                } else if (l_table_name == "engineering_crs") {
9779
17
                    return factory->createEngineeringCRS(l_code);
9780
1.46k
                } else if (l_table_name == "conversion") {
9781
481
                    return factory->createConversion(l_code);
9782
983
                } else if (l_table_name == "grid_transformation" ||
9783
743
                           l_table_name == "helmert_transformation" ||
9784
266
                           l_table_name == "other_transformation" ||
9785
983
                           l_table_name == "concatenated_operation") {
9786
983
                    return factory->createCoordinateOperation(l_code, true);
9787
983
                }
9788
0
                throw std::runtime_error("Unsupported table_name");
9789
31.2k
            };
9790
31.2k
            const auto obj = getObject(table_name, code);
9791
31.2k
            if (metadata::Identifier::isEquivalentName(
9792
31.2k
                    obj->nameStr().c_str(), searchedName.c_str(), false)) {
9793
20.1k
                countExactMatch++;
9794
20.1k
            } else if (metadata::Identifier::isEquivalentName(
9795
11.0k
                           name.c_str(), searchedName.c_str(), false)) {
9796
33
                countExactMatchOnAlias++;
9797
33
            }
9798
9799
31.2k
            const auto objPtr = obj.get();
9800
31.2k
            if (res.empty()) {
9801
21.5k
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9802
21.5k
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9803
7.80k
                hashCodeFirstMatch = 0;
9804
7.80k
            }
9805
9806
31.2k
            res.emplace_back(PairObjectName(obj, name));
9807
31.2k
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9808
1.10k
                break;
9809
1.10k
            }
9810
31.2k
        }
9811
9812
        // If we found several objects that are an exact match, and all objects
9813
        // have the same type, and we are not in approximate mode, only keep the
9814
        // objects with the exact name match.
9815
58.2k
        if ((countExactMatch + countExactMatchOnAlias) >= 1 &&
9816
20.1k
            hashCodeFirstMatch != 0 && !approximateMatch) {
9817
20.1k
            std::list<PairObjectName> resTmp;
9818
20.1k
            bool biggerDifferencesAllowed = (countExactMatch == 0);
9819
20.1k
            for (const auto &pair : res) {
9820
20.1k
                if (metadata::Identifier::isEquivalentName(
9821
20.1k
                        pair.first->nameStr().c_str(), searchedName.c_str(),
9822
20.1k
                        biggerDifferencesAllowed) ||
9823
16
                    (countExactMatch == 0 &&
9824
16
                     metadata::Identifier::isEquivalentName(
9825
16
                         pair.second.c_str(), searchedName.c_str(),
9826
20.1k
                         biggerDifferencesAllowed))) {
9827
20.1k
                    resTmp.emplace_back(pair);
9828
20.1k
                }
9829
20.1k
            }
9830
20.1k
            res = std::move(resTmp);
9831
20.1k
        }
9832
58.2k
    }
9833
9834
58.3k
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9835
18.2k
        const auto &aName = a.first->nameStr();
9836
18.2k
        const auto &bName = b.first->nameStr();
9837
9838
18.2k
        if (aName.size() < bName.size()) {
9839
536
            return true;
9840
536
        }
9841
17.7k
        if (aName.size() > bName.size()) {
9842
9.19k
            return false;
9843
9.19k
        }
9844
9845
8.52k
        const auto &aIds = a.first->identifiers();
9846
8.52k
        const auto &bIds = b.first->identifiers();
9847
8.52k
        if (aIds.size() < bIds.size()) {
9848
0
            return true;
9849
0
        }
9850
8.52k
        if (aIds.size() > bIds.size()) {
9851
0
            return false;
9852
0
        }
9853
8.52k
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9854
8.52k
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9855
8.52k
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9856
8.52k
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9857
8.52k
            if (codeSpaceComparison < 0) {
9858
858
                return true;
9859
858
            }
9860
7.66k
            if (codeSpaceComparison > 0) {
9861
1.00k
                return false;
9862
1.00k
            }
9863
6.66k
            const auto &aCode = aIds[idx]->code();
9864
6.66k
            const auto &bCode = bIds[idx]->code();
9865
6.66k
            const auto codeComparison = aCode.compare(bCode);
9866
6.66k
            if (codeComparison < 0) {
9867
2.00k
                return true;
9868
2.00k
            }
9869
4.65k
            if (codeComparison > 0) {
9870
4.65k
                return false;
9871
4.65k
            }
9872
4.65k
        }
9873
0
        return strcmp(typeid(a.first.get()).name(),
9874
0
                      typeid(b.first.get()).name()) < 0;
9875
8.52k
    };
9876
9877
58.3k
    res.sort(sortLambda);
9878
9879
58.3k
    return res;
9880
60.3k
}
9881
//! @endcond
9882
9883
// ---------------------------------------------------------------------------
9884
9885
/** \brief Return a list of area of use from their name
9886
 *
9887
 * @param name Searched name.
9888
 * @param approximateMatch Whether approximate name identification is allowed.
9889
 * @return list of (auth_name, code) of matched objects.
9890
 * @throw FactoryException in case of error.
9891
 */
9892
std::list<std::pair<std::string, std::string>>
9893
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9894
0
                                        bool approximateMatch) const {
9895
0
    std::string sql(
9896
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9897
0
    ListOfParams params;
9898
0
    if (d->hasAuthorityRestriction()) {
9899
0
        sql += " auth_name = ? AND ";
9900
0
        params.emplace_back(d->authority());
9901
0
    }
9902
0
    sql += "name LIKE ?";
9903
0
    if (!approximateMatch) {
9904
0
        params.push_back(name);
9905
0
    } else {
9906
0
        params.push_back('%' + name + '%');
9907
0
    }
9908
0
    auto sqlRes = d->run(sql, params);
9909
0
    std::list<std::pair<std::string, std::string>> res;
9910
0
    for (const auto &row : sqlRes) {
9911
0
        res.emplace_back(row[0], row[1]);
9912
0
    }
9913
0
    return res;
9914
0
}
9915
9916
// ---------------------------------------------------------------------------
9917
9918
//! @cond Doxygen_Suppress
9919
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9920
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9921
0
    std::string sql(
9922
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9923
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9924
0
        "((semi_minor_axis IS NOT NULL AND "
9925
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9926
0
        "((inv_flattening IS NOT NULL AND "
9927
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9928
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9929
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9930
0
                        ellipsoid->computedInverseFlattening()};
9931
0
    auto sqlRes = d->run(sql, params);
9932
0
    std::list<datum::EllipsoidNNPtr> res;
9933
0
    for (const auto &row : sqlRes) {
9934
0
        const auto &auth_name = row[0];
9935
0
        const auto &code = row[1];
9936
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9937
0
    }
9938
0
    return res;
9939
0
}
9940
//! @endcond
9941
9942
// ---------------------------------------------------------------------------
9943
9944
//! @cond Doxygen_Suppress
9945
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9946
    const std::string &datum_auth_name, const std::string &datum_code,
9947
21.7k
    const std::string &geodetic_crs_type) const {
9948
21.7k
    std::string sql(
9949
21.7k
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9950
21.7k
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9951
21.7k
    ListOfParams params{datum_auth_name, datum_code};
9952
21.7k
    if (d->hasAuthorityRestriction()) {
9953
3.14k
        sql += " AND auth_name = ?";
9954
3.14k
        params.emplace_back(d->authority());
9955
3.14k
    }
9956
21.7k
    if (!geodetic_crs_type.empty()) {
9957
2.52k
        sql += " AND type = ?";
9958
2.52k
        params.emplace_back(geodetic_crs_type);
9959
2.52k
    }
9960
21.7k
    sql += " ORDER BY auth_name, code";
9961
21.7k
    auto sqlRes = d->run(sql, params);
9962
21.7k
    std::list<crs::GeodeticCRSNNPtr> res;
9963
100k
    for (const auto &row : sqlRes) {
9964
100k
        const auto &auth_name = row[0];
9965
100k
        const auto &code = row[1];
9966
100k
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9967
100k
    }
9968
21.7k
    return res;
9969
21.7k
}
9970
//! @endcond
9971
9972
// ---------------------------------------------------------------------------
9973
9974
//! @cond Doxygen_Suppress
9975
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9976
    const datum::GeodeticReferenceFrameNNPtr &datum,
9977
    const std::string &preferredAuthName,
9978
36.5k
    const std::string &geodetic_crs_type) const {
9979
36.5k
    std::list<crs::GeodeticCRSNNPtr> candidates;
9980
36.5k
    const auto &ids = datum->identifiers();
9981
36.5k
    const auto &datumName = datum->nameStr();
9982
36.5k
    if (!ids.empty()) {
9983
21.7k
        for (const auto &id : ids) {
9984
21.7k
            const auto &authName = *(id->codeSpace());
9985
21.7k
            const auto &code = id->code();
9986
21.7k
            if (!authName.empty()) {
9987
21.7k
                const auto tmpFactory =
9988
21.7k
                    (preferredAuthName == authName)
9989
21.7k
                        ? create(databaseContext(), authName)
9990
21.7k
                        : NN_NO_CHECK(d->getSharedFromThis());
9991
21.7k
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9992
21.7k
                    authName, code, geodetic_crs_type);
9993
100k
                for (const auto &candidate : l_candidates) {
9994
100k
                    candidates.emplace_back(candidate);
9995
100k
                }
9996
21.7k
            }
9997
21.7k
        }
9998
21.7k
    } else if (datumName != "unknown" && datumName != "unnamed") {
9999
14.8k
        auto matches = createObjectsFromName(
10000
14.8k
            datumName,
10001
14.8k
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
10002
14.8k
            2);
10003
14.8k
        if (matches.size() == 1) {
10004
0
            const auto &match = matches.front();
10005
0
            if (datum->_isEquivalentTo(match.get(),
10006
0
                                       util::IComparable::Criterion::EQUIVALENT,
10007
0
                                       databaseContext().as_nullable()) &&
10008
0
                !match->identifiers().empty()) {
10009
0
                return createGeodeticCRSFromDatum(
10010
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
10011
0
                        match),
10012
0
                    preferredAuthName, geodetic_crs_type);
10013
0
            }
10014
0
        }
10015
14.8k
    }
10016
36.5k
    return candidates;
10017
36.5k
}
10018
//! @endcond
10019
10020
// ---------------------------------------------------------------------------
10021
10022
//! @cond Doxygen_Suppress
10023
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
10024
114
    const std::string &datum_auth_name, const std::string &datum_code) const {
10025
114
    std::string sql(
10026
114
        "SELECT auth_name, code FROM vertical_crs WHERE "
10027
114
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
10028
114
    ListOfParams params{datum_auth_name, datum_code};
10029
114
    if (d->hasAuthorityRestriction()) {
10030
0
        sql += " AND auth_name = ?";
10031
0
        params.emplace_back(d->authority());
10032
0
    }
10033
114
    auto sqlRes = d->run(sql, params);
10034
114
    std::list<crs::VerticalCRSNNPtr> res;
10035
114
    for (const auto &row : sqlRes) {
10036
104
        const auto &auth_name = row[0];
10037
104
        const auto &code = row[1];
10038
104
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
10039
104
    }
10040
114
    return res;
10041
114
}
10042
//! @endcond
10043
10044
// ---------------------------------------------------------------------------
10045
10046
//! @cond Doxygen_Suppress
10047
std::list<crs::GeodeticCRSNNPtr>
10048
AuthorityFactory::createGeodeticCRSFromEllipsoid(
10049
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
10050
0
    const std::string &geodetic_crs_type) const {
10051
0
    std::string sql(
10052
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
10053
0
        "JOIN geodetic_datum ON "
10054
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
10055
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
10056
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
10057
0
        "geodetic_datum.ellipsoid_code = ? AND "
10058
0
        "geodetic_datum.deprecated = 0 AND "
10059
0
        "geodetic_crs.deprecated = 0");
10060
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
10061
0
    if (d->hasAuthorityRestriction()) {
10062
0
        sql += " AND geodetic_crs.auth_name = ?";
10063
0
        params.emplace_back(d->authority());
10064
0
    }
10065
0
    if (!geodetic_crs_type.empty()) {
10066
0
        sql += " AND geodetic_crs.type = ?";
10067
0
        params.emplace_back(geodetic_crs_type);
10068
0
    }
10069
0
    auto sqlRes = d->run(sql, params);
10070
0
    std::list<crs::GeodeticCRSNNPtr> res;
10071
0
    for (const auto &row : sqlRes) {
10072
0
        const auto &auth_name = row[0];
10073
0
        const auto &code = row[1];
10074
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
10075
0
    }
10076
0
    return res;
10077
0
}
10078
//! @endcond
10079
10080
// ---------------------------------------------------------------------------
10081
10082
//! @cond Doxygen_Suppress
10083
static std::string buildSqlLookForAuthNameCode(
10084
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
10085
0
    const char *prefixField) {
10086
0
    std::string sql("(");
10087
10088
0
    std::set<std::string> authorities;
10089
0
    for (const auto &crs : list) {
10090
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10091
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10092
0
                                   : crs.first->identifiers();
10093
0
        if (!ids.empty()) {
10094
0
            authorities.insert(*(ids[0]->codeSpace()));
10095
0
        }
10096
0
    }
10097
0
    bool firstAuth = true;
10098
0
    for (const auto &auth_name : authorities) {
10099
0
        if (!firstAuth) {
10100
0
            sql += " OR ";
10101
0
        }
10102
0
        firstAuth = false;
10103
0
        sql += "( ";
10104
0
        sql += prefixField;
10105
0
        sql += "auth_name = ? AND ";
10106
0
        sql += prefixField;
10107
0
        sql += "code IN (";
10108
0
        params.emplace_back(auth_name);
10109
0
        bool firstGeodCRSForAuth = true;
10110
0
        for (const auto &crs : list) {
10111
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
10112
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
10113
0
                                       : crs.first->identifiers();
10114
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
10115
0
                if (!firstGeodCRSForAuth) {
10116
0
                    sql += ',';
10117
0
                }
10118
0
                firstGeodCRSForAuth = false;
10119
0
                sql += '?';
10120
0
                params.emplace_back(ids[0]->code());
10121
0
            }
10122
0
        }
10123
0
        sql += "))";
10124
0
    }
10125
0
    sql += ')';
10126
0
    return sql;
10127
0
}
10128
//! @endcond
10129
10130
// ---------------------------------------------------------------------------
10131
10132
//! @cond Doxygen_Suppress
10133
std::list<crs::ProjectedCRSNNPtr>
10134
AuthorityFactory::createProjectedCRSFromExisting(
10135
0
    const crs::ProjectedCRSNNPtr &crs) const {
10136
0
    std::list<crs::ProjectedCRSNNPtr> res;
10137
10138
0
    const auto &conv = crs->derivingConversionRef();
10139
0
    const auto &method = conv->method();
10140
0
    const auto methodEPSGCode = method->getEPSGCode();
10141
0
    if (methodEPSGCode == 0) {
10142
0
        return res;
10143
0
    }
10144
10145
0
    auto lockedThisFactory(d->getSharedFromThis());
10146
0
    assert(lockedThisFactory);
10147
0
    const auto &baseCRS(crs->baseCRS());
10148
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
10149
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
10150
0
    if (geogCRS) {
10151
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
10152
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
10153
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
10154
0
            const auto &unit =
10155
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
10156
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
10157
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
10158
0
                                        geogCRS->nameStr()),
10159
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
10160
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
10161
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
10162
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
10163
0
            auto otherCandidatesGeodCRS =
10164
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
10165
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
10166
0
                                     otherCandidatesGeodCRS.begin(),
10167
0
                                     otherCandidatesGeodCRS.end());
10168
0
        }
10169
0
    }
10170
10171
0
    std::string sql(
10172
0
        "SELECT projected_crs.auth_name, projected_crs.code, "
10173
0
        "projected_crs.name FROM projected_crs "
10174
0
        "JOIN conversion_table conv ON "
10175
0
        "projected_crs.conversion_auth_name = conv.auth_name AND "
10176
0
        "projected_crs.conversion_code = conv.code "
10177
0
        "JOIN geodetic_crs gcrs ON "
10178
0
        "gcrs.auth_name = projected_crs.geodetic_crs_auth_name AND "
10179
0
        "gcrs.code = projected_crs.geodetic_crs_code "
10180
0
        "JOIN geodetic_datum datum ON "
10181
0
        "datum.auth_name = gcrs.datum_auth_name AND "
10182
0
        "datum.code = gcrs.datum_code "
10183
0
        "JOIN ellipsoid ellps ON "
10184
0
        "ellps.auth_name = datum.ellipsoid_auth_name AND "
10185
0
        "ellps.code = datum.ellipsoid_code "
10186
0
        "WHERE "
10187
0
        "abs(ellps.semi_major_axis - ?) <= 1e-4 * ellps.semi_major_axis AND "
10188
0
        "projected_crs.deprecated = 0 AND ");
10189
0
    ListOfParams params;
10190
0
    params.emplace_back(
10191
0
        toString(crs->baseCRS()->ellipsoid()->semiMajorAxis().value()));
10192
0
    if (!candidatesGeodCRS.empty()) {
10193
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10194
0
                                           "projected_crs.geodetic_crs_");
10195
0
        sql += " AND ";
10196
0
    }
10197
0
    sql += "conv.method_auth_name = 'EPSG' AND "
10198
0
           "conv.method_code = ?";
10199
0
    params.emplace_back(toString(methodEPSGCode));
10200
0
    if (d->hasAuthorityRestriction()) {
10201
0
        sql += " AND projected_crs.auth_name = ?";
10202
0
        params.emplace_back(d->authority());
10203
0
    }
10204
10205
0
    int iParam = 0;
10206
0
    bool hasLat1stStd = false;
10207
0
    double lat1stStd = 0;
10208
0
    int iParamLat1stStd = 0;
10209
0
    bool hasLat2ndStd = false;
10210
0
    double lat2ndStd = 0;
10211
0
    int iParamLat2ndStd = 0;
10212
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
10213
0
        iParam++;
10214
0
        auto opParamvalue =
10215
0
            dynamic_cast<const operation::OperationParameterValue *>(
10216
0
                genOpParamvalue.get());
10217
0
        if (!opParamvalue) {
10218
0
            break;
10219
0
        }
10220
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
10221
0
        const auto &parameterValue = opParamvalue->parameterValue();
10222
0
        if (!(paramEPSGCode > 0 &&
10223
0
              parameterValue->type() ==
10224
0
                  operation::ParameterValue::Type::MEASURE)) {
10225
0
            break;
10226
0
        }
10227
0
        const auto &measure = parameterValue->value();
10228
0
        const auto &unit = measure.unit();
10229
0
        if (unit == common::UnitOfMeasure::DEGREE &&
10230
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
10231
0
            if (methodEPSGCode ==
10232
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
10233
                // Special case for standard parallels of LCC_2SP. See below
10234
0
                if (paramEPSGCode ==
10235
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
10236
0
                    hasLat1stStd = true;
10237
0
                    lat1stStd = measure.value();
10238
0
                    iParamLat1stStd = iParam;
10239
0
                    continue;
10240
0
                } else if (paramEPSGCode ==
10241
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
10242
0
                    hasLat2ndStd = true;
10243
0
                    lat2ndStd = measure.value();
10244
0
                    iParamLat2ndStd = iParam;
10245
0
                    continue;
10246
0
                }
10247
0
            }
10248
0
            const auto iParamAsStr(toString(iParam));
10249
0
            sql += " AND conv.param";
10250
0
            sql += iParamAsStr;
10251
0
            sql += "_code = ? AND conv.param";
10252
0
            sql += iParamAsStr;
10253
0
            sql += "_auth_name = 'EPSG' AND conv.param";
10254
0
            sql += iParamAsStr;
10255
0
            sql += "_value BETWEEN ? AND ?";
10256
            // As angles might be expressed with the odd unit EPSG:9110
10257
            // "sexagesimal DMS", we have to provide a broad range
10258
0
            params.emplace_back(toString(paramEPSGCode));
10259
0
            params.emplace_back(measure.value() - 1);
10260
0
            params.emplace_back(measure.value() + 1);
10261
0
        }
10262
0
    }
10263
10264
    // Special case for standard parallels of LCC_2SP: they can be switched
10265
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
10266
0
        hasLat1stStd && hasLat2ndStd) {
10267
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
10268
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
10269
0
        sql += " AND conv.param";
10270
0
        sql += iParam1AsStr;
10271
0
        sql += "_code = ? AND conv.param";
10272
0
        sql += iParam1AsStr;
10273
0
        sql += "_auth_name = 'EPSG' AND conv.param";
10274
0
        sql += iParam2AsStr;
10275
0
        sql += "_code = ? AND conv.param";
10276
0
        sql += iParam2AsStr;
10277
0
        sql += "_auth_name = 'EPSG' AND ((";
10278
0
        params.emplace_back(
10279
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
10280
0
        params.emplace_back(
10281
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
10282
0
        double val1 = lat1stStd;
10283
0
        double val2 = lat2ndStd;
10284
0
        for (int i = 0; i < 2; i++) {
10285
0
            if (i == 1) {
10286
0
                sql += ") OR (";
10287
0
                std::swap(val1, val2);
10288
0
            }
10289
0
            sql += "conv.param";
10290
0
            sql += iParam1AsStr;
10291
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
10292
0
            sql += iParam2AsStr;
10293
0
            sql += "_value BETWEEN ? AND ?";
10294
0
            params.emplace_back(val1 - 1);
10295
0
            params.emplace_back(val1 + 1);
10296
0
            params.emplace_back(val2 - 1);
10297
0
            params.emplace_back(val2 + 1);
10298
0
        }
10299
0
        sql += "))";
10300
0
    }
10301
0
    auto sqlRes = d->run(sql, params);
10302
10303
0
    for (const auto &row : sqlRes) {
10304
0
        const auto &name = row[2];
10305
0
        if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(),
10306
0
                                                   name.c_str())) {
10307
0
            const auto &auth_name = row[0];
10308
0
            const auto &code = row[1];
10309
0
            res.emplace_back(
10310
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10311
0
        }
10312
0
    }
10313
0
    if (!res.empty()) {
10314
0
        return res;
10315
0
    }
10316
10317
0
    params.clear();
10318
10319
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
10320
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
10321
0
    if (!candidatesGeodCRS.empty()) {
10322
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10323
0
                                           "geodetic_crs_");
10324
0
        sql += " AND ";
10325
0
    }
10326
10327
0
    const auto escapeLikeStr = [](const std::string &str) {
10328
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
10329
0
                          "%", "\\%");
10330
0
    };
10331
10332
0
    const auto ellpsSemiMajorStr =
10333
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
10334
10335
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
10336
10337
    // WKT2 definition
10338
0
    {
10339
0
        std::string patternVal("%");
10340
10341
0
        patternVal += ',';
10342
0
        patternVal += ellpsSemiMajorStr;
10343
0
        patternVal += '%';
10344
10345
0
        patternVal += escapeLikeStr(method->nameStr());
10346
0
        patternVal += '%';
10347
10348
0
        params.emplace_back(patternVal);
10349
0
    }
10350
10351
0
    const auto *mapping = getMapping(method.get());
10352
0
    if (mapping && mapping->proj_name_main) {
10353
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
10354
10355
0
        std::string patternVal("%");
10356
0
        patternVal += "proj=";
10357
0
        patternVal += mapping->proj_name_main;
10358
0
        patternVal += '%';
10359
0
        params.emplace_back(patternVal);
10360
10361
        // could be a= or R=
10362
0
        patternVal = "%=";
10363
0
        patternVal += ellpsSemiMajorStr;
10364
0
        patternVal += '%';
10365
0
        params.emplace_back(patternVal);
10366
10367
0
        std::string projEllpsName;
10368
0
        std::string ellpsName;
10369
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
10370
0
                                                            ellpsName)) {
10371
0
            sql += " OR text_definition LIKE ?";
10372
            // Could be ellps= or datum=
10373
0
            patternVal = "%=";
10374
0
            patternVal += projEllpsName;
10375
0
            patternVal += '%';
10376
0
            params.emplace_back(patternVal);
10377
0
        }
10378
10379
0
        sql += "))";
10380
0
    }
10381
10382
    // WKT1_GDAL definition
10383
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
10384
0
    if (wkt1GDALMethodName) {
10385
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10386
0
        std::string patternVal("%");
10387
10388
0
        patternVal += ',';
10389
0
        patternVal += ellpsSemiMajorStr;
10390
0
        patternVal += '%';
10391
10392
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
10393
0
        patternVal += '%';
10394
10395
0
        params.emplace_back(patternVal);
10396
0
    }
10397
10398
    // WKT1_ESRI definition
10399
0
    const char *esriMethodName = conv->getESRIMethodName();
10400
0
    if (esriMethodName) {
10401
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10402
0
        std::string patternVal("%");
10403
10404
0
        patternVal += ',';
10405
0
        patternVal += ellpsSemiMajorStr;
10406
0
        patternVal += '%';
10407
10408
0
        patternVal += escapeLikeStr(esriMethodName);
10409
0
        patternVal += '%';
10410
10411
0
        auto fe =
10412
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
10413
0
        if (*fe == Measure()) {
10414
0
            fe = &conv->parameterValueMeasure(
10415
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
10416
0
        }
10417
0
        if (!(*fe == Measure())) {
10418
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
10419
0
            patternVal +=
10420
0
                toString(fe->convertToUnit(
10421
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
10422
0
                         10);
10423
0
            patternVal += '%';
10424
0
        }
10425
10426
0
        auto lat = &conv->parameterValueMeasure(
10427
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
10428
0
        if (*lat == Measure()) {
10429
0
            lat = &conv->parameterValueMeasure(
10430
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
10431
0
        }
10432
0
        if (!(*lat == Measure())) {
10433
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
10434
0
            const auto &angularUnit =
10435
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
10436
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
10437
0
                    : UnitOfMeasure::DEGREE;
10438
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
10439
0
            patternVal += '%';
10440
0
        }
10441
10442
0
        params.emplace_back(patternVal);
10443
0
    }
10444
0
    sql += ")";
10445
0
    if (d->hasAuthorityRestriction()) {
10446
0
        sql += " AND auth_name = ?";
10447
0
        params.emplace_back(d->authority());
10448
0
    }
10449
10450
0
    auto sqlRes2 = d->run(sql, params);
10451
10452
0
    if (sqlRes.size() <= 200) {
10453
0
        for (const auto &row : sqlRes) {
10454
0
            const auto &auth_name = row[0];
10455
0
            const auto &code = row[1];
10456
0
            res.emplace_back(
10457
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10458
0
        }
10459
0
    }
10460
0
    if (sqlRes2.size() <= 200) {
10461
0
        for (const auto &row : sqlRes2) {
10462
0
            const auto &auth_name = row[0];
10463
0
            const auto &code = row[1];
10464
0
            res.emplace_back(
10465
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10466
0
        }
10467
0
    }
10468
10469
0
    return res;
10470
0
}
10471
10472
// ---------------------------------------------------------------------------
10473
10474
std::list<crs::CompoundCRSNNPtr>
10475
AuthorityFactory::createCompoundCRSFromExisting(
10476
0
    const crs::CompoundCRSNNPtr &crs) const {
10477
0
    std::list<crs::CompoundCRSNNPtr> res;
10478
10479
0
    auto lockedThisFactory(d->getSharedFromThis());
10480
0
    assert(lockedThisFactory);
10481
10482
0
    const auto &components = crs->componentReferenceSystems();
10483
0
    if (components.size() != 2) {
10484
0
        return res;
10485
0
    }
10486
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
10487
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
10488
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
10489
0
        return res;
10490
0
    }
10491
10492
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
10493
0
                    "deprecated = 0 AND ");
10494
0
    ListOfParams params;
10495
0
    bool addAnd = false;
10496
0
    if (!candidatesHorizCRS.empty()) {
10497
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
10498
0
                                           "horiz_crs_");
10499
0
        addAnd = true;
10500
0
    }
10501
0
    if (!candidatesVertCRS.empty()) {
10502
0
        if (addAnd) {
10503
0
            sql += " AND ";
10504
0
        }
10505
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
10506
0
                                           "vertical_crs_");
10507
0
        addAnd = true;
10508
0
    }
10509
0
    if (d->hasAuthorityRestriction()) {
10510
0
        if (addAnd) {
10511
0
            sql += " AND ";
10512
0
        }
10513
0
        sql += "auth_name = ?";
10514
0
        params.emplace_back(d->authority());
10515
0
    }
10516
10517
0
    auto sqlRes = d->run(sql, params);
10518
0
    for (const auto &row : sqlRes) {
10519
0
        const auto &auth_name = row[0];
10520
0
        const auto &code = row[1];
10521
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
10522
0
    }
10523
0
    return res;
10524
0
}
10525
10526
// ---------------------------------------------------------------------------
10527
10528
std::vector<operation::CoordinateOperationNNPtr>
10529
AuthorityFactory::getTransformationsForGeoid(
10530
276
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
10531
276
    std::vector<operation::CoordinateOperationNNPtr> res;
10532
10533
276
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
10534
276
                          "geoid_model WHERE name = ?");
10535
276
    auto sqlRes = d->run(sql, {geoidName});
10536
276
    for (const auto &row : sqlRes) {
10537
0
        const auto &auth_name = row[0];
10538
0
        const auto &code = row[1];
10539
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
10540
0
            code, usePROJAlternativeGridNames));
10541
0
    }
10542
10543
276
    return res;
10544
276
}
10545
10546
// ---------------------------------------------------------------------------
10547
10548
std::vector<operation::PointMotionOperationNNPtr>
10549
AuthorityFactory::getPointMotionOperationsFor(
10550
8
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
10551
8
    std::vector<operation::PointMotionOperationNNPtr> res;
10552
8
    const auto crsList =
10553
8
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
10554
8
                                   /* preferredAuthName = */ std::string(),
10555
8
                                   /* geodetic_crs_type = */ std::string());
10556
8
    if (crsList.empty())
10557
0
        return res;
10558
8
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
10559
8
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
10560
8
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
10561
8
                    "(");
10562
8
    bool addOr = false;
10563
8
    ListOfParams params;
10564
17
    for (const auto &candidateCrs : crsList) {
10565
17
        if (addOr)
10566
9
            sql += " OR ";
10567
17
        addOr = true;
10568
17
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
10569
17
        const auto &ids = candidateCrs->identifiers();
10570
17
        params.emplace_back(*(ids[0]->codeSpace()));
10571
17
        params.emplace_back(ids[0]->code());
10572
17
    }
10573
8
    sql += ")";
10574
8
    if (d->hasAuthorityRestriction()) {
10575
0
        sql += " AND auth_name = ?";
10576
0
        params.emplace_back(d->authority());
10577
0
    }
10578
10579
8
    auto sqlRes = d->run(sql, params);
10580
8
    for (const auto &row : sqlRes) {
10581
0
        const auto &auth_name = row[0];
10582
0
        const auto &code = row[1];
10583
0
        auto pmo =
10584
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
10585
0
                d->createFactory(auth_name)->createCoordinateOperation(
10586
0
                    code, usePROJAlternativeGridNames));
10587
0
        if (pmo) {
10588
0
            res.emplace_back(NN_NO_CHECK(pmo));
10589
0
        }
10590
0
    }
10591
8
    return res;
10592
8
}
10593
10594
//! @endcond
10595
10596
// ---------------------------------------------------------------------------
10597
10598
//! @cond Doxygen_Suppress
10599
1.47k
FactoryException::FactoryException(const char *message) : Exception(message) {}
10600
10601
// ---------------------------------------------------------------------------
10602
10603
FactoryException::FactoryException(const std::string &message)
10604
609
    : Exception(message) {}
10605
10606
// ---------------------------------------------------------------------------
10607
10608
2.08k
FactoryException::~FactoryException() = default;
10609
10610
// ---------------------------------------------------------------------------
10611
10612
0
FactoryException::FactoryException(const FactoryException &) = default;
10613
//! @endcond
10614
10615
// ---------------------------------------------------------------------------
10616
10617
//! @cond Doxygen_Suppress
10618
10619
struct NoSuchAuthorityCodeException::Private {
10620
    std::string authority_;
10621
    std::string code_;
10622
10623
    Private(const std::string &authority, const std::string &code)
10624
609
        : authority_(authority), code_(code) {}
10625
};
10626
10627
// ---------------------------------------------------------------------------
10628
10629
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10630
    const std::string &message, const std::string &authority,
10631
    const std::string &code)
10632
609
    : FactoryException(message), d(std::make_unique<Private>(authority, code)) {
10633
609
}
10634
10635
// ---------------------------------------------------------------------------
10636
10637
609
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
10638
10639
// ---------------------------------------------------------------------------
10640
10641
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10642
    const NoSuchAuthorityCodeException &other)
10643
0
    : FactoryException(other), d(std::make_unique<Private>(*(other.d))) {}
10644
//! @endcond
10645
10646
// ---------------------------------------------------------------------------
10647
10648
/** \brief Returns authority name. */
10649
79
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
10650
79
    return d->authority_;
10651
79
}
10652
10653
// ---------------------------------------------------------------------------
10654
10655
/** \brief Returns authority code. */
10656
79
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
10657
79
    return d->code_;
10658
79
}
10659
10660
// ---------------------------------------------------------------------------
10661
10662
} // namespace io
10663
NS_PROJ_END
10664
10665
// ---------------------------------------------------------------------------
10666
10667
11.4k
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }