Coverage Report

Created: 2026-02-03 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/factory.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/coordinates.hpp"
36
#include "proj/coordinatesystem.hpp"
37
#include "proj/crs.hpp"
38
#include "proj/datum.hpp"
39
#include "proj/io.hpp"
40
#include "proj/metadata.hpp"
41
#include "proj/util.hpp"
42
43
#include "proj/internal/internal.hpp"
44
#include "proj/internal/io_internal.hpp"
45
#include "proj/internal/lru_cache.hpp"
46
#include "proj/internal/tracing.hpp"
47
48
#include "operation/coordinateoperation_internal.hpp"
49
#include "operation/parammappings.hpp"
50
51
#include "filemanager.hpp"
52
#include "sqlite3_utils.hpp"
53
54
#include <algorithm>
55
#include <cmath>
56
#include <cstdlib>
57
#include <cstring>
58
#include <functional>
59
#include <iomanip>
60
#include <limits>
61
#include <locale>
62
#include <map>
63
#include <memory>
64
#include <mutex>
65
#include <sstream> // std::ostringstream
66
#include <stdexcept>
67
#include <string>
68
69
#include "proj_constants.h"
70
71
// PROJ include order is sensitive
72
// clang-format off
73
#include "proj.h"
74
#include "proj_internal.h"
75
// clang-format on
76
77
#include <sqlite3.h>
78
79
#ifdef EMBED_RESOURCE_FILES
80
#include "embedded_resources.h"
81
#endif
82
83
// Custom SQLite VFS as our database is not supposed to be modified in
84
// parallel. This is slightly faster
85
#define ENABLE_CUSTOM_LOCKLESS_VFS
86
87
#if defined(_WIN32) && defined(PROJ_HAS_PTHREADS)
88
#undef PROJ_HAS_PTHREADS
89
#endif
90
91
/* SQLite3 might use seak()+read() or pread[64]() to read data */
92
/* The later allows the same SQLite handle to be safely used in forked */
93
/* children of a parent process, while the former doesn't. */
94
/* So we use pthread_atfork() to set a flag in forked children, to ask them */
95
/* to close and reopen their database handle. */
96
#if defined(PROJ_HAS_PTHREADS) && !defined(SQLITE_USE_PREAD)
97
#include <pthread.h>
98
#define REOPEN_SQLITE_DB_AFTER_FORK
99
#endif
100
101
using namespace NS_PROJ::internal;
102
using namespace NS_PROJ::common;
103
104
NS_PROJ_START
105
namespace io {
106
107
//! @cond Doxygen_Suppress
108
109
// CRS subtypes
110
131k
#define GEOG_2D "geographic 2D"
111
64.6k
#define GEOG_3D "geographic 3D"
112
43.6k
#define GEOCENTRIC "geocentric"
113
393
#define OTHER "other"
114
144
#define PROJECTED "projected"
115
48
#define ENGINEERING "engineering"
116
355
#define VERTICAL "vertical"
117
45
#define COMPOUND "compound"
118
119
0
#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
120
0
#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
121
#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
122
123
// Coordinate system types
124
constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE;
125
constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE;
126
constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE;
127
constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE;
128
constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE;
129
130
// See data/sql/metadata.sql for the semantics of those constants
131
constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
132
// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
133
// must be incremented.
134
constexpr int DATABASE_LAYOUT_VERSION_MINOR = 6;
135
136
constexpr size_t N_MAX_PARAMS = 7;
137
138
#ifdef EMBED_RESOURCE_FILES
139
constexpr const char *EMBEDDED_PROJ_DB = "__embedded_proj_db__";
140
#endif
141
142
// ---------------------------------------------------------------------------
143
144
struct SQLValues {
145
    enum class Type { STRING, INT, DOUBLE };
146
147
    // cppcheck-suppress noExplicitConstructor
148
4.05M
    SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
149
150
    // cppcheck-suppress noExplicitConstructor
151
0
    SQLValues(int value) : type_(Type::INT), int_(value) {}
152
153
    // cppcheck-suppress noExplicitConstructor
154
239k
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
155
156
6.03M
    const Type &type() const { return type_; }
157
158
    // cppcheck-suppress functionStatic
159
5.09M
    const std::string &stringValue() const { return str_; }
160
161
    // cppcheck-suppress functionStatic
162
0
    int intValue() const { return int_; }
163
164
    // cppcheck-suppress functionStatic
165
947k
    double doubleValue() const { return double_; }
166
167
  private:
168
    Type type_;
169
    std::string str_{};
170
    int int_ = 0;
171
    double double_ = 0.0;
172
};
173
174
// ---------------------------------------------------------------------------
175
176
using SQLRow = std::vector<std::string>;
177
using SQLResultSet = std::list<SQLRow>;
178
using ListOfParams = std::list<SQLValues>;
179
180
// ---------------------------------------------------------------------------
181
182
2.17M
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
183
2.17M
    switch (sqlite3_value_type(val)) {
184
2.17M
    case SQLITE_FLOAT:
185
2.17M
        gotVal = true;
186
2.17M
        return sqlite3_value_double(val);
187
188
0
    case SQLITE_INTEGER:
189
0
        gotVal = true;
190
0
        return static_cast<double>(sqlite3_value_int64(val));
191
192
0
    default:
193
0
        gotVal = false;
194
0
        return 0.0;
195
2.17M
    }
196
2.17M
}
197
198
// ---------------------------------------------------------------------------
199
200
static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
201
                                              int /* argc */,
202
46.2k
                                              sqlite3_value **argv) {
203
46.2k
    bool b0, b1, b2, b3;
204
46.2k
    double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
205
46.2k
    double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
206
46.2k
    double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
207
46.2k
    double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
208
46.2k
    if (!b0 || !b1 || !b2 || !b3) {
209
0
        sqlite3_result_null(pContext);
210
0
        return;
211
0
    }
212
    // Deal with area crossing antimeridian
213
46.2k
    if (east_lon < west_lon) {
214
690
        east_lon += 360.0;
215
690
    }
216
    // Integrate cos(lat) between south_lat and north_lat
217
46.2k
    double pseudo_area = (east_lon - west_lon) *
218
46.2k
                         (std::sin(common::Angle(north_lat).getSIValue()) -
219
46.2k
                          std::sin(common::Angle(south_lat).getSIValue()));
220
46.2k
    sqlite3_result_double(pContext, pseudo_area);
221
46.2k
}
222
223
// ---------------------------------------------------------------------------
224
225
static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
226
248k
                                        int /* argc */, sqlite3_value **argv) {
227
248k
    bool b0, b1, b2, b3, b4, b5, b6, b7;
228
248k
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
229
248k
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
230
248k
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
231
248k
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
232
248k
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
233
248k
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
234
248k
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
235
248k
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
236
248k
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
237
0
        sqlite3_result_null(pContext);
238
0
        return;
239
0
    }
240
248k
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
241
248k
                                                         east_lon1, north_lat1);
242
248k
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
243
248k
                                                         east_lon2, north_lat2);
244
248k
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
245
248k
}
246
247
// ---------------------------------------------------------------------------
248
249
class SQLiteHandle {
250
    std::string path_{};
251
    sqlite3 *sqlite_handle_ = nullptr;
252
    bool close_handle_ = true;
253
254
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
255
    bool is_valid_ = true;
256
#endif
257
258
    int nLayoutVersionMajor_ = 0;
259
    int nLayoutVersionMinor_ = 0;
260
261
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
262
    std::unique_ptr<SQLite3VFS> vfs_{};
263
#endif
264
265
    SQLiteHandle(const SQLiteHandle &) = delete;
266
    SQLiteHandle &operator=(const SQLiteHandle &) = delete;
267
268
    SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
269
10.7k
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
270
10.7k
        assert(sqlite_handle_);
271
10.7k
    }
272
273
    // cppcheck-suppress functionStatic
274
    void initialize();
275
276
    SQLResultSet run(const std::string &sql,
277
                     const ListOfParams &parameters = ListOfParams(),
278
                     bool useMaxFloatPrecision = false);
279
280
  public:
281
    ~SQLiteHandle();
282
283
10.7k
    const std::string &path() const { return path_; }
284
285
98.6k
    sqlite3 *handle() { return sqlite_handle_; }
286
287
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
288
1.46M
    bool isValid() const { return is_valid_; }
289
290
0
    void invalidate() { is_valid_ = false; }
291
#endif
292
293
    static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
294
                                              const std::string &path);
295
296
    // might not be shared between thread depending how the handle was opened!
297
    static std::shared_ptr<SQLiteHandle>
298
    initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
299
                     int nLayoutVersionMajor, int nLayoutVersionMinor);
300
301
    static std::unique_ptr<SQLiteHandle>
302
    initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
303
304
    void checkDatabaseLayout(const std::string &mainDbPath,
305
                             const std::string &path,
306
                             const std::string &dbNamePrefix);
307
308
    SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
309
                     const ListOfParams &parameters = ListOfParams(),
310
                     bool useMaxFloatPrecision = false);
311
312
0
    inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
313
0
    inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
314
};
315
316
// ---------------------------------------------------------------------------
317
318
10.7k
SQLiteHandle::~SQLiteHandle() {
319
10.7k
    if (close_handle_) {
320
10.7k
        sqlite3_close(sqlite_handle_);
321
10.7k
    }
322
10.7k
}
323
324
// ---------------------------------------------------------------------------
325
326
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
327
10.7k
                                                 const std::string &pathIn) {
328
329
10.7k
    std::string path(pathIn);
330
10.7k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
331
    // Minimum version for correct performance: 3.11
332
10.7k
    if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
333
0
        pj_log(ctx, PJ_LOG_ERROR,
334
0
               "SQLite3 version is %s, whereas at least 3.11 should be used",
335
0
               sqlite3_libversion());
336
0
    }
337
338
10.7k
    std::string vfsName;
339
10.7k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
340
10.7k
    std::unique_ptr<SQLite3VFS> vfs;
341
10.7k
#endif
342
343
10.7k
#ifdef EMBED_RESOURCE_FILES
344
10.7k
    if (path == EMBEDDED_PROJ_DB && ctx->custom_sqlite3_vfs_name.empty()) {
345
10.7k
        unsigned int proj_db_size = 0;
346
10.7k
        const unsigned char *proj_db = pj_get_embedded_proj_db(&proj_db_size);
347
348
10.7k
        vfs = SQLite3VFS::createMem(proj_db, proj_db_size);
349
10.7k
        if (vfs == nullptr) {
350
0
            throw FactoryException("Open of " + path + " failed");
351
0
        }
352
353
10.7k
        std::ostringstream buffer;
354
10.7k
        buffer << "file:/proj.db?immutable=1&ptr=";
355
10.7k
        buffer << reinterpret_cast<uintptr_t>(proj_db);
356
10.7k
        buffer << "&sz=";
357
10.7k
        buffer << proj_db_size;
358
10.7k
        buffer << "&max=";
359
10.7k
        buffer << proj_db_size;
360
10.7k
        buffer << "&vfs=";
361
10.7k
        buffer << vfs->name();
362
10.7k
        path = buffer.str();
363
10.7k
    } else
364
0
#endif
365
366
0
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
367
0
        if (ctx->custom_sqlite3_vfs_name.empty()) {
368
0
        vfs = SQLite3VFS::create(false, true, true);
369
0
        if (vfs == nullptr) {
370
0
            throw FactoryException("Open of " + path + " failed");
371
0
        }
372
0
        vfsName = vfs->name();
373
0
    } else
374
0
#endif
375
0
    {
376
0
        vfsName = ctx->custom_sqlite3_vfs_name;
377
0
    }
378
10.7k
    sqlite3 *sqlite_handle = nullptr;
379
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
380
10.7k
    if (sqlite3_open_v2(
381
10.7k
            path.c_str(), &sqlite_handle,
382
10.7k
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
383
10.7k
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
384
10.7k
        !sqlite_handle) {
385
0
        if (sqlite_handle != nullptr) {
386
0
            sqlite3_close(sqlite_handle);
387
0
        }
388
0
        throw FactoryException("Open of " + path + " failed");
389
0
    }
390
10.7k
    auto handle =
391
10.7k
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
392
10.7k
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
393
10.7k
    handle->vfs_ = std::move(vfs);
394
10.7k
#endif
395
10.7k
    handle->initialize();
396
10.7k
    handle->path_ = path;
397
10.7k
    handle->checkDatabaseLayout(path, path, std::string());
398
10.7k
    return handle;
399
10.7k
}
400
401
// ---------------------------------------------------------------------------
402
403
std::shared_ptr<SQLiteHandle>
404
SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
405
                               int nLayoutVersionMajor,
406
0
                               int nLayoutVersionMinor) {
407
0
    auto handle = std::shared_ptr<SQLiteHandle>(
408
0
        new SQLiteHandle(sqlite_handle, close_handle));
409
0
    handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
410
0
    handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
411
0
    handle->initialize();
412
0
    return handle;
413
0
}
414
415
// ---------------------------------------------------------------------------
416
417
std::unique_ptr<SQLiteHandle>
418
SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
419
0
                                        bool close_handle) {
420
0
    auto handle = std::unique_ptr<SQLiteHandle>(
421
0
        new SQLiteHandle(sqlite_handle, close_handle));
422
0
    handle->initialize();
423
0
    return handle;
424
0
}
425
426
// ---------------------------------------------------------------------------
427
428
SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
429
                               const ListOfParams &parameters,
430
1.47M
                               bool useMaxFloatPrecision) {
431
1.47M
    int nBindField = 1;
432
6.03M
    for (const auto &param : parameters) {
433
6.03M
        const auto &paramType = param.type();
434
6.03M
        if (paramType == SQLValues::Type::STRING) {
435
5.09M
            const auto &strValue = param.stringValue();
436
5.09M
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
437
5.09M
                              static_cast<int>(strValue.size()),
438
5.09M
                              SQLITE_TRANSIENT);
439
5.09M
        } else if (paramType == SQLValues::Type::INT) {
440
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
441
947k
        } else {
442
947k
            assert(paramType == SQLValues::Type::DOUBLE);
443
947k
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
444
947k
        }
445
6.03M
        nBindField++;
446
6.03M
    }
447
448
#ifdef TRACE_DATABASE
449
    size_t nPos = 0;
450
    std::string sqlSubst(sql);
451
    for (const auto &param : parameters) {
452
        nPos = sqlSubst.find('?', nPos);
453
        assert(nPos != std::string::npos);
454
        std::string strValue;
455
        const auto paramType = param.type();
456
        if (paramType == SQLValues::Type::STRING) {
457
            strValue = '\'' + param.stringValue() + '\'';
458
        } else if (paramType == SQLValues::Type::INT) {
459
            strValue = toString(param.intValue());
460
        } else {
461
            strValue = toString(param.doubleValue());
462
        }
463
        sqlSubst =
464
            sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
465
        nPos += strValue.size();
466
    }
467
    logTrace(sqlSubst, "DATABASE");
468
#endif
469
470
1.47M
    SQLResultSet result;
471
1.47M
    const int column_count = sqlite3_column_count(stmt);
472
66.9M
    while (true) {
473
66.9M
        int ret = sqlite3_step(stmt);
474
66.9M
        if (ret == SQLITE_ROW) {
475
65.4M
            SQLRow row(column_count);
476
466M
            for (int i = 0; i < column_count; i++) {
477
400M
                if (useMaxFloatPrecision &&
478
76.9k
                    sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
479
                    // sqlite3_column_text() does not use maximum precision
480
19.2k
                    std::ostringstream buffer;
481
19.2k
                    buffer.imbue(std::locale::classic());
482
19.2k
                    buffer << std::setprecision(18);
483
19.2k
                    buffer << sqlite3_column_double(stmt, i);
484
19.2k
                    row[i] = buffer.str();
485
400M
                } else {
486
400M
                    const char *txt = reinterpret_cast<const char *>(
487
400M
                        sqlite3_column_text(stmt, i));
488
400M
                    if (txt) {
489
397M
                        row[i] = txt;
490
397M
                    }
491
400M
                }
492
400M
            }
493
65.4M
            result.emplace_back(std::move(row));
494
65.4M
        } else if (ret == SQLITE_DONE) {
495
1.47M
            break;
496
1.47M
        } else {
497
0
            throw FactoryException(std::string("SQLite error [ ")
498
0
                                       .append("code = ")
499
0
                                       .append(internal::toString(ret))
500
0
                                       .append(", msg = ")
501
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
502
0
                                       .append(" ] on ")
503
0
                                       .append(sql));
504
0
        }
505
66.9M
    }
506
1.47M
    return result;
507
1.47M
}
508
509
// ---------------------------------------------------------------------------
510
511
SQLResultSet SQLiteHandle::run(const std::string &sql,
512
                               const ListOfParams &parameters,
513
10.7k
                               bool useMaxFloatPrecision) {
514
10.7k
    sqlite3_stmt *stmt = nullptr;
515
10.7k
    try {
516
10.7k
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
517
10.7k
                               static_cast<int>(sql.size()), &stmt,
518
10.7k
                               nullptr) != SQLITE_OK) {
519
0
            throw FactoryException(std::string("SQLite error [ ")
520
0
                                       .append(sqlite3_errmsg(sqlite_handle_))
521
0
                                       .append(" ] on ")
522
0
                                       .append(sql));
523
0
        }
524
10.7k
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
525
10.7k
        sqlite3_finalize(stmt);
526
10.7k
        return ret;
527
10.7k
    } catch (const std::exception &) {
528
0
        if (stmt)
529
0
            sqlite3_finalize(stmt);
530
0
        throw;
531
0
    }
532
10.7k
}
533
534
// ---------------------------------------------------------------------------
535
536
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
537
                                       const std::string &path,
538
10.7k
                                       const std::string &dbNamePrefix) {
539
10.7k
    if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
540
0
                                     "sqlite_master WHERE name = 'metadata'")
541
0
                                     .empty()) {
542
        // Accept auxiliary databases without metadata table (sparse DBs)
543
0
        return;
544
0
    }
545
10.7k
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
546
10.7k
                   "metadata WHERE key IN "
547
10.7k
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
548
10.7k
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
549
10.7k
    if (res.empty() && !dbNamePrefix.empty()) {
550
        // Accept auxiliary databases without layout metadata.
551
0
        return;
552
0
    }
553
10.7k
    if (res.size() != 2) {
554
0
        throw FactoryException(
555
0
            path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
556
0
                   "DATABASE.LAYOUT.VERSION.MINOR "
557
0
                   "metadata. It comes from another PROJ installation.");
558
0
    }
559
10.7k
    int major = 0;
560
10.7k
    int minor = 0;
561
21.4k
    for (const auto &row : res) {
562
21.4k
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
563
10.7k
            major = atoi(row[1].c_str());
564
10.7k
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
565
10.7k
            minor = atoi(row[1].c_str());
566
10.7k
        }
567
21.4k
    }
568
10.7k
    if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
569
0
        throw FactoryException(
570
0
            path +
571
0
            " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
572
0
            " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
573
0
            " is expected. "
574
0
            "It comes from another PROJ installation.");
575
0
    }
576
577
10.7k
    if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
578
0
        throw FactoryException(
579
0
            path +
580
0
            " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
581
0
            " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
582
0
            " is expected. "
583
0
            "It comes from another PROJ installation.");
584
0
    }
585
586
10.7k
    if (dbNamePrefix.empty()) {
587
10.7k
        nLayoutVersionMajor_ = major;
588
10.7k
        nLayoutVersionMinor_ = minor;
589
10.7k
    } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
590
0
        throw FactoryException(
591
0
            "Auxiliary database " + path +
592
0
            " contains a DATABASE.LAYOUT.VERSION =  " + toString(major) + '.' +
593
0
            toString(minor) +
594
0
            " which is different from the one from the main database " +
595
0
            mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
596
0
            toString(nLayoutVersionMinor_));
597
0
    }
598
10.7k
}
599
600
// ---------------------------------------------------------------------------
601
602
#ifndef SQLITE_DETERMINISTIC
603
#define SQLITE_DETERMINISTIC 0
604
#endif
605
606
10.7k
void SQLiteHandle::initialize() {
607
608
    // There is a bug in sqlite 3.38.0 with some complex queries.
609
    // Cf https://github.com/OSGeo/PROJ/issues/3077
610
    // Disabling Bloom-filter pull-down optimization as suggested in
611
    // https://sqlite.org/forum/forumpost/7d3a75438c
612
10.7k
    const int sqlite3VersionNumber = sqlite3_libversion_number();
613
10.7k
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
614
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
615
0
                             0x100000);
616
0
    }
617
618
10.7k
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
619
10.7k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
620
10.7k
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
621
10.7k
                            nullptr);
622
623
10.7k
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
624
10.7k
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
625
10.7k
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
626
10.7k
}
627
628
// ---------------------------------------------------------------------------
629
630
class SQLiteHandleCache {
631
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
632
    bool firstTime_ = true;
633
#endif
634
635
    std::mutex sMutex_{};
636
637
    // Map dbname to SQLiteHandle
638
    lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
639
640
  public:
641
    static SQLiteHandleCache &get();
642
643
    std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
644
                                            PJ_CONTEXT *ctx);
645
646
    void clear();
647
648
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
649
    void invalidateHandles();
650
#endif
651
};
652
653
// ---------------------------------------------------------------------------
654
655
23.2k
SQLiteHandleCache &SQLiteHandleCache::get() {
656
    // Global cache
657
23.2k
    static SQLiteHandleCache gSQLiteHandleCache;
658
23.2k
    return gSQLiteHandleCache;
659
23.2k
}
660
661
// ---------------------------------------------------------------------------
662
663
12.4k
void SQLiteHandleCache::clear() {
664
12.4k
    std::lock_guard<std::mutex> lock(sMutex_);
665
12.4k
    cache_.clear();
666
12.4k
}
667
668
// ---------------------------------------------------------------------------
669
670
std::shared_ptr<SQLiteHandle>
671
10.7k
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
672
10.7k
    std::lock_guard<std::mutex> lock(sMutex_);
673
674
10.7k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
675
10.7k
    if (firstTime_) {
676
1
        firstTime_ = false;
677
1
        pthread_atfork(
678
1
            []() {
679
                // This mutex needs to be acquired by 'invalidateHandles()'.
680
                // The forking thread needs to own this mutex during the fork.
681
                // Otherwise there's an opporunity for another thread to own
682
                // the mutex during the fork, leaving the child process unable
683
                // to acquire the mutex in invalidateHandles().
684
0
                SQLiteHandleCache::get().sMutex_.lock();
685
0
            },
686
1
            []() { SQLiteHandleCache::get().sMutex_.unlock(); },
687
1
            []() {
688
0
                SQLiteHandleCache::get().sMutex_.unlock();
689
0
                SQLiteHandleCache::get().invalidateHandles();
690
0
            });
691
1
    }
692
10.7k
#endif
693
694
10.7k
    std::shared_ptr<SQLiteHandle> handle;
695
10.7k
    std::string key = path + ctx->custom_sqlite3_vfs_name;
696
10.7k
    if (!cache_.tryGet(key, handle)) {
697
10.7k
        handle = SQLiteHandle::open(ctx, path);
698
10.7k
        cache_.insert(key, handle);
699
10.7k
    }
700
10.7k
    return handle;
701
10.7k
}
702
703
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
704
// ---------------------------------------------------------------------------
705
706
0
void SQLiteHandleCache::invalidateHandles() {
707
0
    std::lock_guard<std::mutex> lock(sMutex_);
708
0
    const auto lambda =
709
0
        [](const lru11::KeyValuePair<std::string, std::shared_ptr<SQLiteHandle>>
710
0
               &kvp) { kvp.value->invalidate(); };
711
0
    cache_.cwalk(lambda);
712
0
    cache_.clear();
713
0
}
714
#endif
715
716
// ---------------------------------------------------------------------------
717
718
struct DatabaseContext::Private {
719
    Private();
720
    ~Private();
721
722
    void open(const std::string &databasePath, PJ_CONTEXT *ctx);
723
    void setHandle(sqlite3 *sqlite_handle);
724
725
    const std::shared_ptr<SQLiteHandle> &handle();
726
727
287k
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
728
10.7k
    void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
729
730
    SQLResultSet run(const std::string &sql,
731
                     const ListOfParams &parameters = ListOfParams(),
732
                     bool useMaxFloatPrecision = false);
733
734
    std::vector<std::string> getDatabaseStructure();
735
736
    // cppcheck-suppress functionStatic
737
0
    const std::string &getPath() const { return databasePath_; }
738
739
    void attachExtraDatabases(
740
        const std::vector<std::string> &auxiliaryDatabasePaths);
741
742
    // Mechanism to detect recursion in calls from
743
    // AuthorityFactory::createXXX() -> createFromUserInput() ->
744
    // AuthorityFactory::createXXX()
745
    struct RecursionDetector {
746
        explicit RecursionDetector(const DatabaseContextNNPtr &context)
747
301
            : dbContext_(context) {
748
301
            if (dbContext_->getPrivate()->recLevel_ == 2) {
749
                // Throw exception before incrementing, since the destructor
750
                // will not be called
751
0
                throw FactoryException("Too many recursive calls");
752
0
            }
753
301
            ++dbContext_->getPrivate()->recLevel_;
754
301
        }
755
756
301
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
757
758
      private:
759
        DatabaseContextNNPtr dbContext_;
760
    };
761
762
137
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
763
137
        return mapCanonicalizeGRFName_;
764
137
    }
765
766
    // cppcheck-suppress functionStatic
767
    common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
768
    // cppcheck-suppress functionStatic
769
    void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
770
771
    // cppcheck-suppress functionStatic
772
    crs::CRSPtr getCRSFromCache(const std::string &code);
773
    // cppcheck-suppress functionStatic
774
    void cache(const std::string &code, const crs::CRSNNPtr &crs);
775
776
    datum::GeodeticReferenceFramePtr
777
    // cppcheck-suppress functionStatic
778
    getGeodeticDatumFromCache(const std::string &code);
779
    // cppcheck-suppress functionStatic
780
    void cache(const std::string &code,
781
               const datum::GeodeticReferenceFrameNNPtr &datum);
782
783
    datum::DatumEnsemblePtr
784
    // cppcheck-suppress functionStatic
785
    getDatumEnsembleFromCache(const std::string &code);
786
    // cppcheck-suppress functionStatic
787
    void cache(const std::string &code,
788
               const datum::DatumEnsembleNNPtr &datumEnsemble);
789
790
    datum::EllipsoidPtr
791
    // cppcheck-suppress functionStatic
792
    getEllipsoidFromCache(const std::string &code);
793
    // cppcheck-suppress functionStatic
794
    void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
795
796
    datum::PrimeMeridianPtr
797
    // cppcheck-suppress functionStatic
798
    getPrimeMeridianFromCache(const std::string &code);
799
    // cppcheck-suppress functionStatic
800
    void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
801
802
    // cppcheck-suppress functionStatic
803
    cs::CoordinateSystemPtr
804
    getCoordinateSystemFromCache(const std::string &code);
805
    // cppcheck-suppress functionStatic
806
    void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
807
808
    // cppcheck-suppress functionStatic
809
    metadata::ExtentPtr getExtentFromCache(const std::string &code);
810
    // cppcheck-suppress functionStatic
811
    void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
812
813
    // cppcheck-suppress functionStatic
814
    bool getCRSToCRSCoordOpFromCache(
815
        const std::string &code,
816
        std::vector<operation::CoordinateOperationNNPtr> &list);
817
    // cppcheck-suppress functionStatic
818
    void cache(const std::string &code,
819
               const std::vector<operation::CoordinateOperationNNPtr> &list);
820
821
    struct GridInfoCache {
822
        std::string fullFilename{};
823
        std::string packageName{};
824
        std::string url{};
825
        bool found = false;
826
        bool directDownload = false;
827
        bool openLicense = false;
828
        bool gridAvailable = false;
829
    };
830
831
    // cppcheck-suppress functionStatic
832
    bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
833
    // cppcheck-suppress functionStatic
834
    void evictGridInfoFromCache(const std::string &code);
835
    // cppcheck-suppress functionStatic
836
    void cache(const std::string &code, const GridInfoCache &info);
837
838
    struct VersionedAuthName {
839
        std::string versionedAuthName{};
840
        std::string authName{};
841
        std::string version{};
842
        int priority = 0;
843
    };
844
    const std::vector<VersionedAuthName> &getCacheAuthNameWithVersion();
845
846
  private:
847
    friend class DatabaseContext;
848
849
    // This is a manual implementation of std::enable_shared_from_this<> that
850
    // avoids publicly deriving from it.
851
    std::weak_ptr<DatabaseContext> self_{};
852
853
    std::string databasePath_{};
854
    std::vector<std::string> auxiliaryDatabasePaths_{};
855
    std::shared_ptr<SQLiteHandle> sqlite_handle_{};
856
    unsigned int queryCounter_ = 0;
857
    std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
858
    PJ_CONTEXT *pjCtxt_ = nullptr;
859
    int recLevel_ = 0;
860
    bool detach_ = false;
861
    std::string lastMetadataValue_{};
862
    std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
863
864
    // Used by startInsertStatementsSession() and related functions
865
    std::string memoryDbForInsertPath_{};
866
    std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
867
868
    using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
869
870
    static constexpr size_t CACHE_SIZE = 128;
871
    LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
872
    LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
873
    LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
874
    LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
875
    LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE};
876
    LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
877
    LRUCacheOfObjects cacheCS_{CACHE_SIZE};
878
    LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
879
    lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
880
        cacheCRSToCrsCoordOp_{CACHE_SIZE};
881
    lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
882
883
    std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
884
885
    lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
886
        CACHE_SIZE};
887
    lru11::Cache<std::string, std::string> cacheNames_{CACHE_SIZE};
888
889
    std::vector<VersionedAuthName> cacheAuthNameWithVersion_{};
890
891
    static void insertIntoCache(LRUCacheOfObjects &cache,
892
                                const std::string &code,
893
                                const util::BaseObjectPtr &obj);
894
895
    static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
896
                             util::BaseObjectPtr &obj);
897
898
    void closeDB() noexcept;
899
900
    void clearCaches();
901
902
    std::string findFreeCode(const std::string &tableName,
903
                             const std::string &authName,
904
                             const std::string &codePrototype);
905
906
    void identify(const DatabaseContextNNPtr &dbContext,
907
                  const cs::CoordinateSystemNNPtr &obj, std::string &authName,
908
                  std::string &code);
909
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
910
                          const cs::CoordinateSystemNNPtr &obj,
911
                          const std::string &ownerType,
912
                          const std::string &ownerAuthName,
913
                          const std::string &ownerCode, std::string &authName,
914
                          std::string &code,
915
                          std::vector<std::string> &sqlStatements);
916
917
    void identify(const DatabaseContextNNPtr &dbContext,
918
                  const common::UnitOfMeasure &obj, std::string &authName,
919
                  std::string &code);
920
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
921
                          const common::UnitOfMeasure &unit,
922
                          const std::string &ownerAuthName,
923
                          std::string &authName, std::string &code,
924
                          std::vector<std::string> &sqlStatements);
925
926
    void appendSql(std::vector<std::string> &sqlStatements,
927
                   const std::string &sql);
928
929
    void
930
    identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj,
931
                           const std::string &tableName,
932
                           const std::string &authName, const std::string &code,
933
                           const std::vector<std::string> &allowedAuthorities,
934
                           std::vector<std::string> &sqlStatements);
935
936
    std::vector<std::string>
937
    getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm,
938
                           const std::string &authName, const std::string &code,
939
                           bool numericCode,
940
                           const std::vector<std::string> &allowedAuthorities);
941
942
    std::vector<std::string>
943
    getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid,
944
                           const std::string &authName, const std::string &code,
945
                           bool numericCode,
946
                           const std::vector<std::string> &allowedAuthorities);
947
948
    std::vector<std::string>
949
    getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum,
950
                           const std::string &authName, const std::string &code,
951
                           bool numericCode,
952
                           const std::vector<std::string> &allowedAuthorities);
953
954
    std::vector<std::string>
955
    getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble,
956
                           const std::string &authName, const std::string &code,
957
                           bool numericCode,
958
                           const std::vector<std::string> &allowedAuthorities);
959
960
    std::vector<std::string>
961
    getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs,
962
                           const std::string &authName, const std::string &code,
963
                           bool numericCode,
964
                           const std::vector<std::string> &allowedAuthorities);
965
966
    std::vector<std::string>
967
    getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs,
968
                           const std::string &authName, const std::string &code,
969
                           bool numericCode,
970
                           const std::vector<std::string> &allowedAuthorities);
971
972
    std::vector<std::string>
973
    getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum,
974
                           const std::string &authName, const std::string &code,
975
                           bool numericCode,
976
                           const std::vector<std::string> &allowedAuthorities);
977
978
    std::vector<std::string>
979
    getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs,
980
                           const std::string &authName, const std::string &code,
981
                           bool numericCode,
982
                           const std::vector<std::string> &allowedAuthorities);
983
984
    std::vector<std::string>
985
    getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs,
986
                           const std::string &authName, const std::string &code,
987
                           bool numericCode,
988
                           const std::vector<std::string> &allowedAuthorities);
989
990
    Private(const Private &) = delete;
991
    Private &operator=(const Private &) = delete;
992
};
993
994
// ---------------------------------------------------------------------------
995
996
10.7k
DatabaseContext::Private::Private() = default;
997
998
// ---------------------------------------------------------------------------
999
1000
10.7k
DatabaseContext::Private::~Private() {
1001
10.7k
    assert(recLevel_ == 0);
1002
1003
10.7k
    closeDB();
1004
10.7k
}
1005
1006
// ---------------------------------------------------------------------------
1007
1008
10.7k
void DatabaseContext::Private::closeDB() noexcept {
1009
1010
10.7k
    if (detach_) {
1011
        // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
1012
        // a crash in TEST(factory, attachExtraDatabases_auxiliary)
1013
        // due to possible wrong caching of key info.
1014
        // The bug is specific to using a memory file with shared cache as an
1015
        // auxiliary DB.
1016
        // The fix was likely in 3.8.8
1017
        // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
1018
        // But just after 3.8.2,
1019
        // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
1020
        // also seemed to hide the issue.
1021
        // Detaching a database hides the issue, not sure if it is by chance...
1022
0
        try {
1023
0
            run("DETACH DATABASE db_0");
1024
0
        } catch (...) {
1025
0
        }
1026
0
        detach_ = false;
1027
0
    }
1028
1029
98.6k
    for (auto &pair : mapSqlToStatement_) {
1030
98.6k
        sqlite3_finalize(pair.second);
1031
98.6k
    }
1032
10.7k
    mapSqlToStatement_.clear();
1033
1034
10.7k
    sqlite_handle_.reset();
1035
10.7k
}
1036
1037
// ---------------------------------------------------------------------------
1038
1039
0
void DatabaseContext::Private::clearCaches() {
1040
1041
0
    cacheUOM_.clear();
1042
0
    cacheCRS_.clear();
1043
0
    cacheEllipsoid_.clear();
1044
0
    cacheGeodeticDatum_.clear();
1045
0
    cacheDatumEnsemble_.clear();
1046
0
    cachePrimeMeridian_.clear();
1047
0
    cacheCS_.clear();
1048
0
    cacheExtent_.clear();
1049
0
    cacheCRSToCrsCoordOp_.clear();
1050
0
    cacheGridInfo_.clear();
1051
0
    cacheAllowedAuthorities_.clear();
1052
0
    cacheAliasNames_.clear();
1053
0
    cacheNames_.clear();
1054
0
}
1055
1056
// ---------------------------------------------------------------------------
1057
1058
1.46M
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
1059
1.46M
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
1060
1.46M
    if (sqlite_handle_ && !sqlite_handle_->isValid()) {
1061
0
        closeDB();
1062
0
        open(databasePath_, pjCtxt_);
1063
0
        if (!auxiliaryDatabasePaths_.empty()) {
1064
0
            attachExtraDatabases(auxiliaryDatabasePaths_);
1065
0
        }
1066
0
    }
1067
1.46M
#endif
1068
1.46M
    return sqlite_handle_;
1069
1.46M
}
1070
1071
// ---------------------------------------------------------------------------
1072
1073
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1074
                                               const std::string &code,
1075
151k
                                               const util::BaseObjectPtr &obj) {
1076
151k
    cache.insert(code, obj);
1077
151k
}
1078
1079
// ---------------------------------------------------------------------------
1080
1081
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1082
                                            const std::string &code,
1083
2.34M
                                            util::BaseObjectPtr &obj) {
1084
2.34M
    cache.tryGet(code, obj);
1085
2.34M
}
1086
1087
// ---------------------------------------------------------------------------
1088
1089
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1090
    const std::string &code,
1091
257k
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1092
257k
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1093
257k
}
1094
1095
// ---------------------------------------------------------------------------
1096
1097
void DatabaseContext::Private::cache(
1098
    const std::string &code,
1099
64.6k
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1100
64.6k
    cacheCRSToCrsCoordOp_.insert(code, list);
1101
64.6k
}
1102
1103
// ---------------------------------------------------------------------------
1104
1105
1.08M
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1106
1.08M
    util::BaseObjectPtr obj;
1107
1.08M
    getFromCache(cacheCRS_, code, obj);
1108
1.08M
    return std::static_pointer_cast<crs::CRS>(obj);
1109
1.08M
}
1110
1111
// ---------------------------------------------------------------------------
1112
1113
void DatabaseContext::Private::cache(const std::string &code,
1114
53.6k
                                     const crs::CRSNNPtr &crs) {
1115
53.6k
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1116
53.6k
}
1117
1118
// ---------------------------------------------------------------------------
1119
1120
common::UnitOfMeasurePtr
1121
207k
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1122
207k
    util::BaseObjectPtr obj;
1123
207k
    getFromCache(cacheUOM_, code, obj);
1124
207k
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1125
207k
}
1126
1127
// ---------------------------------------------------------------------------
1128
1129
void DatabaseContext::Private::cache(const std::string &code,
1130
19.2k
                                     const common::UnitOfMeasureNNPtr &uom) {
1131
19.2k
    insertIntoCache(cacheUOM_, code, uom.as_nullable());
1132
19.2k
}
1133
1134
// ---------------------------------------------------------------------------
1135
1136
datum::GeodeticReferenceFramePtr
1137
437k
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1138
437k
    util::BaseObjectPtr obj;
1139
437k
    getFromCache(cacheGeodeticDatum_, code, obj);
1140
437k
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1141
437k
}
1142
1143
// ---------------------------------------------------------------------------
1144
1145
void DatabaseContext::Private::cache(
1146
44.4k
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1147
44.4k
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1148
44.4k
}
1149
1150
// ---------------------------------------------------------------------------
1151
1152
datum::DatumEnsemblePtr
1153
464k
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1154
464k
    util::BaseObjectPtr obj;
1155
464k
    getFromCache(cacheDatumEnsemble_, code, obj);
1156
464k
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1157
464k
}
1158
1159
// ---------------------------------------------------------------------------
1160
1161
void DatabaseContext::Private::cache(
1162
3.67k
    const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) {
1163
3.67k
    insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable());
1164
3.67k
}
1165
1166
// ---------------------------------------------------------------------------
1167
1168
datum::EllipsoidPtr
1169
44.6k
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1170
44.6k
    util::BaseObjectPtr obj;
1171
44.6k
    getFromCache(cacheEllipsoid_, code, obj);
1172
44.6k
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1173
44.6k
}
1174
1175
// ---------------------------------------------------------------------------
1176
1177
void DatabaseContext::Private::cache(const std::string &code,
1178
8.05k
                                     const datum::EllipsoidNNPtr &ellps) {
1179
8.05k
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1180
8.05k
}
1181
1182
// ---------------------------------------------------------------------------
1183
1184
datum::PrimeMeridianPtr
1185
44.4k
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1186
44.4k
    util::BaseObjectPtr obj;
1187
44.4k
    getFromCache(cachePrimeMeridian_, code, obj);
1188
44.4k
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1189
44.4k
}
1190
1191
// ---------------------------------------------------------------------------
1192
1193
void DatabaseContext::Private::cache(const std::string &code,
1194
4.56k
                                     const datum::PrimeMeridianNNPtr &pm) {
1195
4.56k
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1196
4.56k
}
1197
1198
// ---------------------------------------------------------------------------
1199
1200
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1201
53.3k
    const std::string &code) {
1202
53.3k
    util::BaseObjectPtr obj;
1203
53.3k
    getFromCache(cacheCS_, code, obj);
1204
53.3k
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1205
53.3k
}
1206
1207
// ---------------------------------------------------------------------------
1208
1209
void DatabaseContext::Private::cache(const std::string &code,
1210
17.8k
                                     const cs::CoordinateSystemNNPtr &cs) {
1211
17.8k
    insertIntoCache(cacheCS_, code, cs.as_nullable());
1212
17.8k
}
1213
1214
// ---------------------------------------------------------------------------
1215
1216
metadata::ExtentPtr
1217
0
DatabaseContext::Private::getExtentFromCache(const std::string &code) {
1218
0
    util::BaseObjectPtr obj;
1219
0
    getFromCache(cacheExtent_, code, obj);
1220
0
    return std::static_pointer_cast<metadata::Extent>(obj);
1221
0
}
1222
1223
// ---------------------------------------------------------------------------
1224
1225
void DatabaseContext::Private::cache(const std::string &code,
1226
0
                                     const metadata::ExtentNNPtr &extent) {
1227
0
    insertIntoCache(cacheExtent_, code, extent.as_nullable());
1228
0
}
1229
1230
// ---------------------------------------------------------------------------
1231
1232
bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
1233
176k
                                                    GridInfoCache &info) {
1234
176k
    return cacheGridInfo_.tryGet(code, info);
1235
176k
}
1236
1237
// ---------------------------------------------------------------------------
1238
1239
0
void DatabaseContext::Private::evictGridInfoFromCache(const std::string &code) {
1240
0
    cacheGridInfo_.remove(code);
1241
0
}
1242
1243
// ---------------------------------------------------------------------------
1244
1245
void DatabaseContext::Private::cache(const std::string &code,
1246
40.1k
                                     const GridInfoCache &info) {
1247
40.1k
    cacheGridInfo_.insert(code, info);
1248
40.1k
}
1249
1250
// ---------------------------------------------------------------------------
1251
1252
void DatabaseContext::Private::open(const std::string &databasePath,
1253
10.7k
                                    PJ_CONTEXT *ctx) {
1254
10.7k
    if (!ctx) {
1255
0
        ctx = pj_get_default_ctx();
1256
0
    }
1257
1258
10.7k
    setPjCtxt(ctx);
1259
10.7k
    std::string path(databasePath);
1260
10.7k
    if (path.empty()) {
1261
10.7k
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
1262
10.7k
        path.resize(2048);
1263
10.7k
        const bool found =
1264
10.7k
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
1265
10.7k
        path.resize(strlen(path.c_str()));
1266
10.7k
        if (!found)
1267
10.7k
#endif
1268
10.7k
        {
1269
10.7k
#ifdef EMBED_RESOURCE_FILES
1270
10.7k
            path = EMBEDDED_PROJ_DB;
1271
#else
1272
            throw FactoryException("Cannot find proj.db");
1273
#endif
1274
10.7k
        }
1275
10.7k
    }
1276
1277
10.7k
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1278
1279
10.7k
    databasePath_ = sqlite_handle_->path();
1280
10.7k
}
1281
1282
// ---------------------------------------------------------------------------
1283
1284
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1285
1286
0
    assert(sqlite_handle);
1287
0
    assert(!sqlite_handle_);
1288
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1289
0
}
1290
1291
// ---------------------------------------------------------------------------
1292
1293
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1294
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1295
0
                                           memoryDbForInsertPath_.empty()
1296
0
                                       ? ""
1297
0
                                       : "db_0.");
1298
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1299
0
                        "sqlite_master WHERE type = ");
1300
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1301
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1302
0
    std::vector<std::string> res;
1303
0
    for (const auto &objectType : objectTypes) {
1304
0
        const auto sqlRes = run(sqlBegin + objectType);
1305
0
        for (const auto &row : sqlRes) {
1306
0
            res.emplace_back(row[0]);
1307
0
        }
1308
0
    }
1309
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1310
0
        res.emplace_back(
1311
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1312
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1313
0
        res.emplace_back(
1314
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1315
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1316
0
    }
1317
0
    return res;
1318
0
}
1319
1320
// ---------------------------------------------------------------------------
1321
1322
void DatabaseContext::Private::attachExtraDatabases(
1323
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1324
1325
0
    auto l_handle = handle();
1326
0
    assert(l_handle);
1327
1328
0
    auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN "
1329
0
                      "('table', 'view') "
1330
0
                      "AND name NOT LIKE 'sqlite_stat%'");
1331
1332
0
    struct TableStructure {
1333
0
        std::string name{};
1334
0
        bool isTable = false;
1335
0
        std::string sql{};
1336
0
        std::vector<std::string> columns{};
1337
0
    };
1338
0
    std::vector<TableStructure> tablesStructure;
1339
0
    for (const auto &rowTable : tables) {
1340
0
        TableStructure tableStructure;
1341
0
        tableStructure.name = rowTable[0];
1342
0
        tableStructure.isTable = rowTable[1] == "table";
1343
0
        tableStructure.sql = rowTable[2];
1344
0
        auto tableInfo =
1345
0
            run("PRAGMA table_info(\"" +
1346
0
                replaceAll(tableStructure.name, "\"", "\"\"") + "\")");
1347
0
        for (const auto &rowCol : tableInfo) {
1348
0
            const auto &colName = rowCol[1];
1349
0
            tableStructure.columns.push_back(colName);
1350
0
        }
1351
0
        tablesStructure.push_back(std::move(tableStructure));
1352
0
    }
1353
1354
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1355
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1356
1357
0
    closeDB();
1358
0
    if (auxiliaryDatabasePaths.empty()) {
1359
0
        open(databasePath_, pjCtxt());
1360
0
        return;
1361
0
    }
1362
1363
0
    sqlite3 *sqlite_handle = nullptr;
1364
0
    sqlite3_open_v2(
1365
0
        ":memory:", &sqlite_handle,
1366
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1367
0
    if (!sqlite_handle) {
1368
0
        throw FactoryException("cannot create in memory database");
1369
0
    }
1370
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1371
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1372
0
    l_handle = sqlite_handle_;
1373
1374
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1375
0
    detach_ = true;
1376
0
    int count = 1;
1377
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1378
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1379
0
        std::string sql = "ATTACH DATABASE ? AS ";
1380
0
        sql += attachedDbName;
1381
0
        count++;
1382
0
        run(sql, {otherDbPath});
1383
1384
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1385
0
                                      attachedDbName + '.');
1386
0
    }
1387
1388
0
    for (const auto &tableStructure : tablesStructure) {
1389
0
        if (tableStructure.isTable) {
1390
0
            std::string sql("CREATE TEMP VIEW ");
1391
0
            sql += tableStructure.name;
1392
0
            sql += " AS ";
1393
0
            for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1394
0
                std::string selectFromAux("SELECT ");
1395
0
                bool firstCol = true;
1396
0
                for (const auto &colName : tableStructure.columns) {
1397
0
                    if (!firstCol) {
1398
0
                        selectFromAux += ", ";
1399
0
                    }
1400
0
                    firstCol = false;
1401
0
                    selectFromAux += colName;
1402
0
                }
1403
0
                selectFromAux += " FROM db_";
1404
0
                selectFromAux += toString(static_cast<int>(i));
1405
0
                selectFromAux += ".";
1406
0
                selectFromAux += tableStructure.name;
1407
1408
0
                try {
1409
                    // Check that the request will succeed. In case of 'sparse'
1410
                    // databases...
1411
0
                    run(selectFromAux + " LIMIT 0");
1412
1413
0
                    if (i > 0) {
1414
0
                        if (tableStructure.name == "conversion_method")
1415
0
                            sql += " UNION ";
1416
0
                        else
1417
0
                            sql += " UNION ALL ";
1418
0
                    }
1419
0
                    sql += selectFromAux;
1420
0
                } catch (const std::exception &) {
1421
0
                }
1422
0
            }
1423
0
            run(sql);
1424
0
        } else {
1425
0
            run(replaceAll(tableStructure.sql, "CREATE VIEW",
1426
0
                           "CREATE TEMP VIEW"));
1427
0
        }
1428
0
    }
1429
0
}
1430
1431
// ---------------------------------------------------------------------------
1432
1433
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1434
                                           const ListOfParams &parameters,
1435
1.46M
                                           bool useMaxFloatPrecision) {
1436
1437
1.46M
    auto l_handle = handle();
1438
1.46M
    assert(l_handle);
1439
1440
1.46M
    sqlite3_stmt *stmt = nullptr;
1441
1.46M
    auto iter = mapSqlToStatement_.find(sql);
1442
1.46M
    if (iter != mapSqlToStatement_.end()) {
1443
1.36M
        stmt = iter->second;
1444
1.36M
        sqlite3_reset(stmt);
1445
1.36M
    } else {
1446
98.6k
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1447
98.6k
                               static_cast<int>(sql.size()), &stmt,
1448
98.6k
                               nullptr) != SQLITE_OK) {
1449
0
            throw FactoryException(
1450
0
                std::string("SQLite error [ ")
1451
0
                    .append(sqlite3_errmsg(l_handle->handle()))
1452
0
                    .append(" ] on ")
1453
0
                    .append(sql));
1454
0
        }
1455
98.6k
        mapSqlToStatement_.insert(
1456
98.6k
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1457
98.6k
    }
1458
1459
1.46M
    ++queryCounter_;
1460
1461
1.46M
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1462
1.46M
}
1463
1464
// ---------------------------------------------------------------------------
1465
1466
0
static std::string formatStatement(const char *fmt, ...) {
1467
0
    std::string res;
1468
0
    va_list args;
1469
0
    va_start(args, fmt);
1470
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1471
0
        if (fmt[i] == '%') {
1472
0
            if (fmt[i + 1] == '%') {
1473
0
                res += '%';
1474
0
            } else if (fmt[i + 1] == 'q') {
1475
0
                const char *arg = va_arg(args, const char *);
1476
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1477
0
                    if (arg[j] == '\'')
1478
0
                        res += arg[j];
1479
0
                    res += arg[j];
1480
0
                }
1481
0
            } else if (fmt[i + 1] == 'Q') {
1482
0
                const char *arg = va_arg(args, const char *);
1483
0
                if (arg == nullptr)
1484
0
                    res += "NULL";
1485
0
                else {
1486
0
                    res += '\'';
1487
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1488
0
                        if (arg[j] == '\'')
1489
0
                            res += arg[j];
1490
0
                        res += arg[j];
1491
0
                    }
1492
0
                    res += '\'';
1493
0
                }
1494
0
            } else if (fmt[i + 1] == 's') {
1495
0
                const char *arg = va_arg(args, const char *);
1496
0
                res += arg;
1497
0
            } else if (fmt[i + 1] == 'f') {
1498
0
                const double arg = va_arg(args, double);
1499
0
                res += toString(arg);
1500
0
            } else if (fmt[i + 1] == 'd') {
1501
0
                const int arg = va_arg(args, int);
1502
0
                res += toString(arg);
1503
0
            } else {
1504
0
                va_end(args);
1505
0
                throw FactoryException(
1506
0
                    "Unsupported formatter in formatStatement()");
1507
0
            }
1508
0
            ++i;
1509
0
        } else {
1510
0
            res += fmt[i];
1511
0
        }
1512
0
    }
1513
0
    va_end(args);
1514
0
    return res;
1515
0
}
1516
1517
// ---------------------------------------------------------------------------
1518
1519
void DatabaseContext::Private::appendSql(
1520
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1521
0
    sqlStatements.emplace_back(sql);
1522
0
    char *errMsg = nullptr;
1523
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1524
0
                     &errMsg) != SQLITE_OK) {
1525
0
        std::string s("Cannot execute " + sql);
1526
0
        if (errMsg) {
1527
0
            s += " : ";
1528
0
            s += errMsg;
1529
0
        }
1530
0
        sqlite3_free(errMsg);
1531
0
        throw FactoryException(s);
1532
0
    }
1533
0
    sqlite3_free(errMsg);
1534
0
}
1535
1536
// ---------------------------------------------------------------------------
1537
1538
static void identifyFromNameOrCode(
1539
    const DatabaseContextNNPtr &dbContext,
1540
    const std::vector<std::string> &allowedAuthorities,
1541
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1542
    std::function<std::shared_ptr<util::IComparable>(
1543
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1544
        instantiateFunc,
1545
    AuthorityFactory::ObjectType objType, std::string &authName,
1546
0
    std::string &code) {
1547
1548
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1549
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1550
1551
0
    for (const auto &id : obj->identifiers()) {
1552
0
        try {
1553
0
            const auto &idAuthName = *(id->codeSpace());
1554
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1555
0
                          allowedAuthoritiesTmp.end(),
1556
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1557
0
                const auto factory =
1558
0
                    AuthorityFactory::create(dbContext, idAuthName);
1559
0
                if (instantiateFunc(factory, id->code())
1560
0
                        ->isEquivalentTo(
1561
0
                            obj.get(),
1562
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1563
0
                    authName = idAuthName;
1564
0
                    code = id->code();
1565
0
                    return;
1566
0
                }
1567
0
            }
1568
0
        } catch (const std::exception &) {
1569
0
        }
1570
0
    }
1571
1572
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1573
0
        const auto factory =
1574
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1575
0
        const auto candidates =
1576
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1577
0
        for (const auto &candidate : candidates) {
1578
0
            const auto &ids = candidate->identifiers();
1579
0
            if (!ids.empty() &&
1580
0
                candidate->isEquivalentTo(
1581
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1582
0
                const auto &id = ids.front();
1583
0
                authName = *(id->codeSpace());
1584
0
                code = id->code();
1585
0
                return;
1586
0
            }
1587
0
        }
1588
0
    }
1589
0
}
1590
1591
// ---------------------------------------------------------------------------
1592
1593
static void
1594
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1595
                       const std::vector<std::string> &allowedAuthorities,
1596
                       const std::string &authNameParent,
1597
                       const datum::DatumEnsembleNNPtr &obj,
1598
0
                       std::string &authName, std::string &code) {
1599
0
    const char *type = "geodetic_datum";
1600
0
    if (!obj->datums().empty() &&
1601
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1602
0
            obj->datums().front().get())) {
1603
0
        type = "vertical_datum";
1604
0
    }
1605
0
    const auto instantiateFunc =
1606
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1607
0
                const std::string &lCode) {
1608
0
            return util::nn_static_pointer_cast<util::IComparable>(
1609
0
                authFactory->createDatumEnsemble(lCode, type));
1610
0
        };
1611
0
    identifyFromNameOrCode(
1612
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1613
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1614
0
}
1615
1616
// ---------------------------------------------------------------------------
1617
1618
static void
1619
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1620
                       const std::vector<std::string> &allowedAuthorities,
1621
                       const std::string &authNameParent,
1622
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1623
0
                       std::string &authName, std::string &code) {
1624
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1625
0
                                    const std::string &lCode) {
1626
0
        return util::nn_static_pointer_cast<util::IComparable>(
1627
0
            authFactory->createGeodeticDatum(lCode));
1628
0
    };
1629
0
    identifyFromNameOrCode(
1630
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1631
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1632
0
}
1633
1634
// ---------------------------------------------------------------------------
1635
1636
static void
1637
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1638
                       const std::vector<std::string> &allowedAuthorities,
1639
                       const std::string &authNameParent,
1640
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1641
0
                       std::string &code) {
1642
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1643
0
                                    const std::string &lCode) {
1644
0
        return util::nn_static_pointer_cast<util::IComparable>(
1645
0
            authFactory->createEllipsoid(lCode));
1646
0
    };
1647
0
    identifyFromNameOrCode(
1648
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1649
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1650
0
}
1651
1652
// ---------------------------------------------------------------------------
1653
1654
static void
1655
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1656
                       const std::vector<std::string> &allowedAuthorities,
1657
                       const std::string &authNameParent,
1658
                       const datum::PrimeMeridianNNPtr &obj,
1659
0
                       std::string &authName, std::string &code) {
1660
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1661
0
                                    const std::string &lCode) {
1662
0
        return util::nn_static_pointer_cast<util::IComparable>(
1663
0
            authFactory->createPrimeMeridian(lCode));
1664
0
    };
1665
0
    identifyFromNameOrCode(
1666
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1667
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1668
0
}
1669
1670
// ---------------------------------------------------------------------------
1671
1672
static void
1673
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1674
                       const std::vector<std::string> &allowedAuthorities,
1675
                       const std::string &authNameParent,
1676
                       const datum::VerticalReferenceFrameNNPtr &obj,
1677
0
                       std::string &authName, std::string &code) {
1678
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1679
0
                                    const std::string &lCode) {
1680
0
        return util::nn_static_pointer_cast<util::IComparable>(
1681
0
            authFactory->createVerticalDatum(lCode));
1682
0
    };
1683
0
    identifyFromNameOrCode(
1684
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1685
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1686
0
}
1687
1688
// ---------------------------------------------------------------------------
1689
1690
static void
1691
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1692
                       const std::vector<std::string> &allowedAuthorities,
1693
                       const std::string &authNameParent,
1694
                       const datum::DatumNNPtr &obj, std::string &authName,
1695
0
                       std::string &code) {
1696
0
    if (const auto geodeticDatum =
1697
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1698
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1699
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1700
0
    } else if (const auto verticalDatum =
1701
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1702
0
                       obj)) {
1703
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1704
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1705
0
    } else {
1706
0
        throw FactoryException("Unhandled type of datum");
1707
0
    }
1708
0
}
1709
1710
// ---------------------------------------------------------------------------
1711
1712
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1713
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1714
0
        return CS_TYPE_ELLIPSOIDAL;
1715
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1716
0
        return CS_TYPE_CARTESIAN;
1717
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1718
0
        return CS_TYPE_VERTICAL;
1719
0
    }
1720
0
    return nullptr;
1721
0
}
1722
1723
// ---------------------------------------------------------------------------
1724
1725
std::string
1726
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1727
                                       const std::string &authName,
1728
0
                                       const std::string &codePrototype) {
1729
0
    std::string code(codePrototype);
1730
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1731
0
            {authName, code})
1732
0
            .empty()) {
1733
0
        return code;
1734
0
    }
1735
1736
0
    for (int counter = 2; counter < 10; counter++) {
1737
0
        code = codePrototype + '_' + toString(counter);
1738
0
        if (run("SELECT 1 FROM " + tableName +
1739
0
                    " WHERE auth_name = ? AND code = ?",
1740
0
                {authName, code})
1741
0
                .empty()) {
1742
0
            return code;
1743
0
        }
1744
0
    }
1745
1746
    // shouldn't happen hopefully...
1747
0
    throw FactoryException("Cannot insert " + tableName +
1748
0
                           ": too many similar codes");
1749
0
}
1750
1751
// ---------------------------------------------------------------------------
1752
1753
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1754
0
    switch (unit.type()) {
1755
0
    case common::UnitOfMeasure::Type::LINEAR:
1756
0
        return "length";
1757
1758
0
    case common::UnitOfMeasure::Type::ANGULAR:
1759
0
        return "angle";
1760
1761
0
    case common::UnitOfMeasure::Type::SCALE:
1762
0
        return "scale";
1763
1764
0
    case common::UnitOfMeasure::Type::TIME:
1765
0
        return "time";
1766
1767
0
    default:
1768
0
        break;
1769
0
    }
1770
0
    return nullptr;
1771
0
}
1772
1773
// ---------------------------------------------------------------------------
1774
1775
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1776
                                        const common::UnitOfMeasure &obj,
1777
                                        std::string &authName,
1778
0
                                        std::string &code) {
1779
    // Identify quickly a few well-known units
1780
0
    const double convFactor = obj.conversionToSI();
1781
0
    switch (obj.type()) {
1782
0
    case common::UnitOfMeasure::Type::LINEAR: {
1783
0
        if (convFactor == 1.0) {
1784
0
            authName = metadata::Identifier::EPSG;
1785
0
            code = "9001";
1786
0
            return;
1787
0
        }
1788
0
        break;
1789
0
    }
1790
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1791
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1792
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1793
0
            1e-10 * CONV_FACTOR_DEGREE) {
1794
0
            authName = metadata::Identifier::EPSG;
1795
0
            code = "9102";
1796
0
            return;
1797
0
        }
1798
0
        break;
1799
0
    }
1800
0
    case common::UnitOfMeasure::Type::SCALE: {
1801
0
        if (convFactor == 1.0) {
1802
0
            authName = metadata::Identifier::EPSG;
1803
0
            code = "9201";
1804
0
            return;
1805
0
        }
1806
0
        break;
1807
0
    }
1808
0
    default:
1809
0
        break;
1810
0
    }
1811
1812
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1813
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1814
0
    ListOfParams params{convFactor};
1815
0
    const char *type = getUnitDatabaseType(obj);
1816
0
    if (type) {
1817
0
        sql += " AND type = ?";
1818
0
        params.emplace_back(std::string(type));
1819
0
    }
1820
0
    sql += " ORDER BY auth_name, code";
1821
0
    const auto res = run(sql, params);
1822
0
    for (const auto &row : res) {
1823
0
        const auto &rowAuthName = row[0];
1824
0
        const auto &rowCode = row[1];
1825
0
        const auto tmpAuthFactory =
1826
0
            AuthorityFactory::create(dbContext, rowAuthName);
1827
0
        try {
1828
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1829
0
            authName = rowAuthName;
1830
0
            code = rowCode;
1831
0
            return;
1832
0
        } catch (const std::exception &) {
1833
0
        }
1834
0
    }
1835
0
}
1836
1837
// ---------------------------------------------------------------------------
1838
1839
void DatabaseContext::Private::identifyOrInsert(
1840
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1841
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1842
0
    std::vector<std::string> &sqlStatements) {
1843
0
    authName = unit.codeSpace();
1844
0
    code = unit.code();
1845
0
    if (authName.empty()) {
1846
0
        identify(dbContext, unit, authName, code);
1847
0
    }
1848
0
    if (!authName.empty()) {
1849
0
        return;
1850
0
    }
1851
0
    const char *type = getUnitDatabaseType(unit);
1852
0
    if (type == nullptr) {
1853
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1854
0
    }
1855
1856
    // Insert new record
1857
0
    authName = ownerAuthName;
1858
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1859
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1860
1861
0
    const auto sql = formatStatement(
1862
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1863
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1864
0
        unit.conversionToSI());
1865
0
    appendSql(sqlStatements, sql);
1866
0
}
1867
1868
// ---------------------------------------------------------------------------
1869
1870
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1871
                                        const cs::CoordinateSystemNNPtr &obj,
1872
                                        std::string &authName,
1873
0
                                        std::string &code) {
1874
1875
0
    const auto &axisList = obj->axisList();
1876
0
    if (axisList.size() == 1U &&
1877
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1878
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1879
0
        (axisList[0]->nameStr() == "Up" ||
1880
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1881
        // preferred coordinate system for gravity-related height
1882
0
        authName = metadata::Identifier::EPSG;
1883
0
        code = "6499";
1884
0
        return;
1885
0
    }
1886
1887
0
    std::string sql(
1888
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1889
0
    ListOfParams params{static_cast<int>(axisList.size())};
1890
0
    const char *type = getCSDatabaseType(obj);
1891
0
    if (type) {
1892
0
        sql += " AND type = ?";
1893
0
        params.emplace_back(std::string(type));
1894
0
    }
1895
0
    sql += " ORDER BY auth_name, code";
1896
0
    const auto res = run(sql, params);
1897
0
    for (const auto &row : res) {
1898
0
        const auto &rowAuthName = row[0];
1899
0
        const auto &rowCode = row[1];
1900
0
        const auto tmpAuthFactory =
1901
0
            AuthorityFactory::create(dbContext, rowAuthName);
1902
0
        try {
1903
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1904
0
            if (cs->_isEquivalentTo(obj.get(),
1905
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1906
0
                authName = rowAuthName;
1907
0
                code = rowCode;
1908
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1909
                    // preferred coordinate system for cartesian
1910
                    // Easting, Northing
1911
0
                    return;
1912
0
                }
1913
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1914
                    // preferred coordinate system for geographic lat, long
1915
0
                    return;
1916
0
                }
1917
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1918
                    // preferred coordinate system for geographic lat, long, h
1919
0
                    return;
1920
0
                }
1921
0
            }
1922
0
        } catch (const std::exception &) {
1923
0
        }
1924
0
    }
1925
0
}
1926
1927
// ---------------------------------------------------------------------------
1928
1929
void DatabaseContext::Private::identifyOrInsert(
1930
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1931
    const std::string &ownerType, const std::string &ownerAuthName,
1932
    const std::string &ownerCode, std::string &authName, std::string &code,
1933
0
    std::vector<std::string> &sqlStatements) {
1934
1935
0
    identify(dbContext, obj, authName, code);
1936
0
    if (!authName.empty()) {
1937
0
        return;
1938
0
    }
1939
1940
0
    const char *type = getCSDatabaseType(obj);
1941
0
    if (type == nullptr) {
1942
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1943
0
    }
1944
1945
    // Insert new record in coordinate_system
1946
0
    authName = ownerAuthName;
1947
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1948
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1949
1950
0
    const auto &axisList = obj->axisList();
1951
0
    {
1952
0
        const auto sql = formatStatement(
1953
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1954
0
            authName.c_str(), code.c_str(), type,
1955
0
            static_cast<int>(axisList.size()));
1956
0
        appendSql(sqlStatements, sql);
1957
0
    }
1958
1959
    // Insert new records for the axis
1960
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1961
0
        const auto &axis = axisList[i];
1962
0
        std::string uomAuthName;
1963
0
        std::string uomCode;
1964
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1965
0
                         uomCode, sqlStatements);
1966
0
        const auto sql = formatStatement(
1967
0
            "INSERT INTO axis VALUES("
1968
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1969
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1970
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1971
0
            axis->direction().toString().c_str(), authName.c_str(),
1972
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1973
0
        appendSql(sqlStatements, sql);
1974
0
    }
1975
0
}
1976
1977
// ---------------------------------------------------------------------------
1978
1979
static void
1980
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1981
                          const std::string &authName, std::string &sql,
1982
0
                          ListOfParams &params) {
1983
0
    sql += "auth_name IN (?";
1984
0
    params.emplace_back(authName);
1985
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1986
0
        sql += ",?";
1987
0
        params.emplace_back(allowedAuthority);
1988
0
    }
1989
0
    sql += ')';
1990
0
}
1991
1992
// ---------------------------------------------------------------------------
1993
1994
void DatabaseContext::Private::identifyOrInsertUsages(
1995
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1996
    const std::string &authName, const std::string &code,
1997
    const std::vector<std::string> &allowedAuthorities,
1998
0
    std::vector<std::string> &sqlStatements) {
1999
2000
0
    std::string usageCode("USAGE_");
2001
0
    const std::string upperTableName(toupper(tableName));
2002
0
    if (!starts_with(code, upperTableName)) {
2003
0
        usageCode += upperTableName;
2004
0
        usageCode += '_';
2005
0
    }
2006
0
    usageCode += code;
2007
2008
0
    const auto &domains = obj->domains();
2009
0
    if (domains.empty()) {
2010
0
        const auto sql =
2011
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2012
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
2013
0
                            authName.c_str(), usageCode.c_str(),
2014
0
                            tableName.c_str(), authName.c_str(), code.c_str());
2015
0
        appendSql(sqlStatements, sql);
2016
0
        return;
2017
0
    }
2018
2019
0
    int usageCounter = 1;
2020
0
    for (const auto &domain : domains) {
2021
0
        std::string scopeAuthName;
2022
0
        std::string scopeCode;
2023
0
        const auto &scope = domain->scope();
2024
0
        if (scope.has_value()) {
2025
0
            std::string sql =
2026
0
                "SELECT auth_name, code, "
2027
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2028
0
                "AS order_idx "
2029
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
2030
0
            ListOfParams params{*scope};
2031
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2032
0
                                      params);
2033
0
            sql += " ORDER BY order_idx, auth_name, code";
2034
0
            const auto rows = run(sql, params);
2035
0
            if (!rows.empty()) {
2036
0
                const auto &row = rows.front();
2037
0
                scopeAuthName = row[0];
2038
0
                scopeCode = row[1];
2039
0
            } else {
2040
0
                scopeAuthName = authName;
2041
0
                scopeCode = "SCOPE_";
2042
0
                scopeCode += tableName;
2043
0
                scopeCode += '_';
2044
0
                scopeCode += code;
2045
0
                const auto sqlToInsert = formatStatement(
2046
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
2047
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
2048
0
                appendSql(sqlStatements, sqlToInsert);
2049
0
            }
2050
0
        } else {
2051
0
            scopeAuthName = "PROJ";
2052
0
            scopeCode = "SCOPE_UNKNOWN";
2053
0
        }
2054
2055
0
        std::string extentAuthName("PROJ");
2056
0
        std::string extentCode("EXTENT_UNKNOWN");
2057
0
        const auto &extent = domain->domainOfValidity();
2058
0
        if (extent) {
2059
0
            const auto &geogElts = extent->geographicElements();
2060
0
            if (!geogElts.empty()) {
2061
0
                const auto bbox =
2062
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
2063
0
                        geogElts.front().get());
2064
0
                if (bbox) {
2065
0
                    std::string sql =
2066
0
                        "SELECT auth_name, code, "
2067
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2068
0
                        "AS order_idx "
2069
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
2070
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
2071
0
                        "AND ";
2072
0
                    ListOfParams params{
2073
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
2074
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
2075
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2076
0
                                              params);
2077
0
                    sql += " ORDER BY order_idx, auth_name, code";
2078
0
                    const auto rows = run(sql, params);
2079
0
                    if (!rows.empty()) {
2080
0
                        const auto &row = rows.front();
2081
0
                        extentAuthName = row[0];
2082
0
                        extentCode = row[1];
2083
0
                    } else {
2084
0
                        extentAuthName = authName;
2085
0
                        extentCode = "EXTENT_";
2086
0
                        extentCode += tableName;
2087
0
                        extentCode += '_';
2088
0
                        extentCode += code;
2089
0
                        std::string description(*(extent->description()));
2090
0
                        if (description.empty()) {
2091
0
                            description = "unknown";
2092
0
                        }
2093
0
                        const auto sqlToInsert = formatStatement(
2094
0
                            "INSERT INTO extent "
2095
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
2096
0
                            extentAuthName.c_str(), extentCode.c_str(),
2097
0
                            description.c_str(), description.c_str(),
2098
0
                            bbox->southBoundLatitude(),
2099
0
                            bbox->northBoundLatitude(),
2100
0
                            bbox->westBoundLongitude(),
2101
0
                            bbox->eastBoundLongitude());
2102
0
                        appendSql(sqlStatements, sqlToInsert);
2103
0
                    }
2104
0
                }
2105
0
            }
2106
0
        }
2107
2108
0
        if (domains.size() > 1) {
2109
0
            usageCode += '_';
2110
0
            usageCode += toString(usageCounter);
2111
0
        }
2112
0
        const auto sql = formatStatement(
2113
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2114
0
            "'%q','%q','%q','%q');",
2115
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2116
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2117
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2118
0
        appendSql(sqlStatements, sql);
2119
2120
0
        usageCounter++;
2121
0
    }
2122
0
}
2123
2124
// ---------------------------------------------------------------------------
2125
2126
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2127
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2128
    const std::string &code, bool /*numericCode*/,
2129
0
    const std::vector<std::string> &allowedAuthorities) {
2130
2131
0
    const auto self = NN_NO_CHECK(self_.lock());
2132
2133
    // Check if the object is already known under that code
2134
0
    std::string pmAuthName;
2135
0
    std::string pmCode;
2136
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2137
0
                           pmCode);
2138
0
    if (pmAuthName == authName && pmCode == code) {
2139
0
        return {};
2140
0
    }
2141
2142
0
    std::vector<std::string> sqlStatements;
2143
2144
    // Insert new record in prime_meridian table
2145
0
    std::string uomAuthName;
2146
0
    std::string uomCode;
2147
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2148
0
                     uomCode, sqlStatements);
2149
2150
0
    const auto sql = formatStatement(
2151
0
        "INSERT INTO prime_meridian VALUES("
2152
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2153
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2154
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2155
0
    appendSql(sqlStatements, sql);
2156
2157
0
    return sqlStatements;
2158
0
}
2159
2160
// ---------------------------------------------------------------------------
2161
2162
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2163
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2164
    const std::string &code, bool /*numericCode*/,
2165
0
    const std::vector<std::string> &allowedAuthorities) {
2166
2167
0
    const auto self = NN_NO_CHECK(self_.lock());
2168
2169
    // Check if the object is already known under that code
2170
0
    std::string ellipsoidAuthName;
2171
0
    std::string ellipsoidCode;
2172
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2173
0
                           ellipsoidAuthName, ellipsoidCode);
2174
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2175
0
        return {};
2176
0
    }
2177
2178
0
    std::vector<std::string> sqlStatements;
2179
2180
    // Find or insert celestial body
2181
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2182
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2183
0
    constexpr double tolerance = 0.005;
2184
0
    std::string bodyAuthName;
2185
0
    std::string bodyCode;
2186
0
    auto res = run("SELECT auth_name, code, "
2187
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2188
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2189
0
                   {semiMajorAxisMetre, tolerance});
2190
0
    if (!res.empty()) {
2191
0
        const auto &row = res.front();
2192
0
        bodyAuthName = row[0];
2193
0
        bodyCode = row[1];
2194
0
    } else {
2195
0
        bodyAuthName = authName;
2196
0
        bodyCode = "BODY_" + code;
2197
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2198
0
        const auto sql = formatStatement(
2199
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2200
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2201
0
            semiMajorAxisMetre);
2202
0
        appendSql(sqlStatements, sql);
2203
0
    }
2204
2205
    // Insert new record in ellipsoid table
2206
0
    std::string uomAuthName;
2207
0
    std::string uomCode;
2208
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2209
0
                     sqlStatements);
2210
0
    std::string invFlattening("NULL");
2211
0
    std::string semiMinorAxis("NULL");
2212
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2213
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2214
0
    } else {
2215
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2216
0
    }
2217
2218
0
    const auto sql = formatStatement(
2219
0
        "INSERT INTO ellipsoid VALUES("
2220
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2221
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2222
0
        "", // description
2223
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2224
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2225
0
        semiMinorAxis.c_str());
2226
0
    appendSql(sqlStatements, sql);
2227
2228
0
    return sqlStatements;
2229
0
}
2230
2231
// ---------------------------------------------------------------------------
2232
2233
0
static std::string anchorEpochToStr(double val) {
2234
0
    constexpr int BUF_SIZE = 16;
2235
0
    char szBuffer[BUF_SIZE];
2236
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2237
0
    return szBuffer;
2238
0
}
2239
2240
// ---------------------------------------------------------------------------
2241
2242
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2243
    const datum::GeodeticReferenceFrameNNPtr &datum,
2244
    const std::string &authName, const std::string &code, bool numericCode,
2245
0
    const std::vector<std::string> &allowedAuthorities) {
2246
2247
0
    const auto self = NN_NO_CHECK(self_.lock());
2248
2249
    // Check if the object is already known under that code
2250
0
    std::string datumAuthName;
2251
0
    std::string datumCode;
2252
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2253
0
                           datumAuthName, datumCode);
2254
0
    if (datumAuthName == authName && datumCode == code) {
2255
0
        return {};
2256
0
    }
2257
2258
0
    std::vector<std::string> sqlStatements;
2259
2260
    // Find or insert ellipsoid
2261
0
    std::string ellipsoidAuthName;
2262
0
    std::string ellipsoidCode;
2263
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2264
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2265
0
                           ellipsoidAuthName, ellipsoidCode);
2266
0
    if (ellipsoidAuthName.empty()) {
2267
0
        ellipsoidAuthName = authName;
2268
0
        if (numericCode) {
2269
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2270
0
                                                  ellipsoidAuthName, true);
2271
0
        } else {
2272
0
            ellipsoidCode = "ELLPS_" + code;
2273
0
        }
2274
0
        sqlStatements = self->getInsertStatementsFor(
2275
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2276
0
            allowedAuthorities);
2277
0
    }
2278
2279
    // Find or insert prime meridian
2280
0
    std::string pmAuthName;
2281
0
    std::string pmCode;
2282
0
    const auto &pmOfDatum = datum->primeMeridian();
2283
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2284
0
                           pmAuthName, pmCode);
2285
0
    if (pmAuthName.empty()) {
2286
0
        pmAuthName = authName;
2287
0
        if (numericCode) {
2288
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2289
0
        } else {
2290
0
            pmCode = "PM_" + code;
2291
0
        }
2292
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2293
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2294
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2295
0
                             sqlStatementsTmp.end());
2296
0
    }
2297
2298
    // Insert new record in geodetic_datum table
2299
0
    std::string publicationDate("NULL");
2300
0
    if (datum->publicationDate().has_value()) {
2301
0
        publicationDate = '\'';
2302
0
        publicationDate +=
2303
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2304
0
        publicationDate += '\'';
2305
0
    }
2306
0
    std::string frameReferenceEpoch("NULL");
2307
0
    const auto dynamicDatum =
2308
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2309
0
    if (dynamicDatum) {
2310
0
        frameReferenceEpoch =
2311
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2312
0
    }
2313
0
    const std::string anchor(*(datum->anchorDefinition()));
2314
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2315
0
    const auto sql = formatStatement(
2316
0
        "INSERT INTO geodetic_datum VALUES("
2317
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2318
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2319
0
        "", // description
2320
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2321
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
2322
0
        anchor.empty() ? nullptr : anchor.c_str(),
2323
0
        anchorEpoch.has_value()
2324
0
            ? anchorEpochToStr(
2325
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2326
0
                  .c_str()
2327
0
            : "NULL");
2328
0
    appendSql(sqlStatements, sql);
2329
2330
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2331
0
                           allowedAuthorities, sqlStatements);
2332
2333
0
    return sqlStatements;
2334
0
}
2335
2336
// ---------------------------------------------------------------------------
2337
2338
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2339
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2340
    const std::string &code, bool numericCode,
2341
0
    const std::vector<std::string> &allowedAuthorities) {
2342
0
    const auto self = NN_NO_CHECK(self_.lock());
2343
2344
    // Check if the object is already known under that code
2345
0
    std::string datumAuthName;
2346
0
    std::string datumCode;
2347
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2348
0
                           datumAuthName, datumCode);
2349
0
    if (datumAuthName == authName && datumCode == code) {
2350
0
        return {};
2351
0
    }
2352
2353
0
    std::vector<std::string> sqlStatements;
2354
2355
0
    const auto &members = ensemble->datums();
2356
0
    assert(!members.empty());
2357
2358
0
    int counter = 1;
2359
0
    std::vector<std::pair<std::string, std::string>> membersId;
2360
0
    for (const auto &member : members) {
2361
0
        std::string memberAuthName;
2362
0
        std::string memberCode;
2363
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2364
0
                               memberAuthName, memberCode);
2365
0
        if (memberAuthName.empty()) {
2366
0
            memberAuthName = authName;
2367
0
            if (numericCode) {
2368
0
                memberCode =
2369
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2370
0
            } else {
2371
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2372
0
            }
2373
0
            const auto sqlStatementsTmp =
2374
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2375
0
                                             numericCode, allowedAuthorities);
2376
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2377
0
                                 sqlStatementsTmp.end());
2378
0
        }
2379
2380
0
        membersId.emplace_back(
2381
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2382
2383
0
        ++counter;
2384
0
    }
2385
2386
0
    const bool isGeodetic =
2387
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2388
0
            members.front()) != nullptr;
2389
2390
    // Insert new record in geodetic_datum/vertical_datum table
2391
0
    const double accuracy =
2392
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2393
0
    if (isGeodetic) {
2394
0
        const auto firstDatum =
2395
0
            AuthorityFactory::create(self, membersId.front().first)
2396
0
                ->createGeodeticDatum(membersId.front().second);
2397
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2398
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2399
0
        assert(!ellipsoidIds.empty());
2400
0
        const std::string &ellipsoidAuthName =
2401
0
            *(ellipsoidIds.front()->codeSpace());
2402
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2403
0
        const auto &pm = firstDatum->primeMeridian();
2404
0
        const auto &pmIds = pm->identifiers();
2405
0
        assert(!pmIds.empty());
2406
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2407
0
        const std::string &pmCode = pmIds.front()->code();
2408
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2409
0
        const util::optional<common::Measure> &anchorEpoch =
2410
0
            firstDatum->anchorEpoch();
2411
0
        const auto sql = formatStatement(
2412
0
            "INSERT INTO geodetic_datum VALUES("
2413
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2414
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2415
0
            "", // description
2416
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2417
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2418
0
            anchor.empty() ? nullptr : anchor.c_str(),
2419
0
            anchorEpoch.has_value()
2420
0
                ? anchorEpochToStr(
2421
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2422
0
                      .c_str()
2423
0
                : "NULL");
2424
0
        appendSql(sqlStatements, sql);
2425
0
    } else {
2426
0
        const auto firstDatum =
2427
0
            AuthorityFactory::create(self, membersId.front().first)
2428
0
                ->createVerticalDatum(membersId.front().second);
2429
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2430
0
        const util::optional<common::Measure> &anchorEpoch =
2431
0
            firstDatum->anchorEpoch();
2432
0
        const auto sql = formatStatement(
2433
0
            "INSERT INTO vertical_datum VALUES("
2434
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2435
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2436
0
            "", // description
2437
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2438
0
            anchorEpoch.has_value()
2439
0
                ? anchorEpochToStr(
2440
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2441
0
                      .c_str()
2442
0
                : "NULL");
2443
0
        appendSql(sqlStatements, sql);
2444
0
    }
2445
0
    identifyOrInsertUsages(ensemble,
2446
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2447
0
                           authName, code, allowedAuthorities, sqlStatements);
2448
2449
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2450
0
                                       : "vertical_datum_ensemble_member";
2451
0
    counter = 1;
2452
0
    for (const auto &authCodePair : membersId) {
2453
0
        const auto sql = formatStatement(
2454
0
            "INSERT INTO %s VALUES("
2455
0
            "'%q','%q','%q','%q',%d);",
2456
0
            tableName, authName.c_str(), code.c_str(),
2457
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2458
0
        appendSql(sqlStatements, sql);
2459
0
        ++counter;
2460
0
    }
2461
2462
0
    return sqlStatements;
2463
0
}
2464
2465
// ---------------------------------------------------------------------------
2466
2467
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2468
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2469
    const std::string &code, bool numericCode,
2470
0
    const std::vector<std::string> &allowedAuthorities) {
2471
2472
0
    const auto self = NN_NO_CHECK(self_.lock());
2473
2474
0
    std::vector<std::string> sqlStatements;
2475
2476
    // Find or insert datum/datum ensemble
2477
0
    std::string datumAuthName;
2478
0
    std::string datumCode;
2479
0
    const auto &ensemble = crs->datumEnsemble();
2480
0
    if (ensemble) {
2481
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2482
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2483
0
                               datumAuthName, datumCode);
2484
0
        if (datumAuthName.empty()) {
2485
0
            datumAuthName = authName;
2486
0
            if (numericCode) {
2487
0
                datumCode =
2488
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2489
0
            } else {
2490
0
                datumCode = "GEODETIC_DATUM_" + code;
2491
0
            }
2492
0
            sqlStatements = self->getInsertStatementsFor(
2493
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2494
0
                allowedAuthorities);
2495
0
        }
2496
0
    } else {
2497
0
        const auto &datum = crs->datum();
2498
0
        assert(datum);
2499
0
        const auto datumNN = NN_NO_CHECK(datum);
2500
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2501
0
                               datumAuthName, datumCode);
2502
0
        if (datumAuthName.empty()) {
2503
0
            datumAuthName = authName;
2504
0
            if (numericCode) {
2505
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2506
0
            } else {
2507
0
                datumCode = "GEODETIC_DATUM_" + code;
2508
0
            }
2509
0
            sqlStatements =
2510
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2511
0
                                             numericCode, allowedAuthorities);
2512
0
        }
2513
0
    }
2514
2515
    // Find or insert coordinate system
2516
0
    const auto &coordinateSystem = crs->coordinateSystem();
2517
0
    std::string csAuthName;
2518
0
    std::string csCode;
2519
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2520
0
                     csAuthName, csCode, sqlStatements);
2521
2522
0
    const char *type = GEOG_2D;
2523
0
    if (coordinateSystem->axisList().size() == 3) {
2524
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2525
0
            type = GEOG_3D;
2526
0
        } else {
2527
0
            type = GEOCENTRIC;
2528
0
        }
2529
0
    }
2530
2531
    // Insert new record in geodetic_crs table
2532
0
    const auto sql =
2533
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2534
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2535
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2536
0
                        "", // description
2537
0
                        type, csAuthName.c_str(), csCode.c_str(),
2538
0
                        datumAuthName.c_str(), datumCode.c_str());
2539
0
    appendSql(sqlStatements, sql);
2540
2541
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2542
0
                           allowedAuthorities, sqlStatements);
2543
0
    return sqlStatements;
2544
0
}
2545
2546
// ---------------------------------------------------------------------------
2547
2548
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2549
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2550
    const std::string &code, bool numericCode,
2551
0
    const std::vector<std::string> &allowedAuthorities) {
2552
2553
0
    const auto self = NN_NO_CHECK(self_.lock());
2554
2555
0
    std::vector<std::string> sqlStatements;
2556
2557
    // Find or insert base geodetic CRS
2558
0
    const auto &baseCRS = crs->baseCRS();
2559
0
    std::string geodAuthName;
2560
0
    std::string geodCode;
2561
2562
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2563
0
    allowedAuthoritiesTmp.emplace_back(authName);
2564
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2565
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2566
0
        const auto candidates = baseCRS->identify(factory);
2567
0
        for (const auto &candidate : candidates) {
2568
0
            if (candidate.second == 100) {
2569
0
                const auto &ids = candidate.first->identifiers();
2570
0
                if (!ids.empty()) {
2571
0
                    const auto &id = ids.front();
2572
0
                    geodAuthName = *(id->codeSpace());
2573
0
                    geodCode = id->code();
2574
0
                    break;
2575
0
                }
2576
0
            }
2577
0
            if (!geodAuthName.empty()) {
2578
0
                break;
2579
0
            }
2580
0
        }
2581
0
    }
2582
0
    if (geodAuthName.empty()) {
2583
0
        geodAuthName = authName;
2584
0
        geodCode = "GEODETIC_CRS_" + code;
2585
0
        sqlStatements = self->getInsertStatementsFor(
2586
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2587
0
    }
2588
2589
    // Insert new record in conversion table
2590
0
    const auto &conversion = crs->derivingConversionRef();
2591
0
    std::string convAuthName(authName);
2592
0
    std::string convCode("CONVERSION_" + code);
2593
0
    if (numericCode) {
2594
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2595
0
    }
2596
0
    {
2597
0
        const auto &method = conversion->method();
2598
0
        const auto &methodIds = method->identifiers();
2599
0
        std::string methodAuthName;
2600
0
        std::string methodCode;
2601
0
        const operation::MethodMapping *methodMapping = nullptr;
2602
0
        if (methodIds.empty()) {
2603
0
            const int epsgCode = method->getEPSGCode();
2604
0
            if (epsgCode > 0) {
2605
0
                methodAuthName = metadata::Identifier::EPSG;
2606
0
                methodCode = toString(epsgCode);
2607
0
            } else {
2608
0
                const auto &methodName = method->nameStr();
2609
0
                size_t nProjectionMethodMappings = 0;
2610
0
                const auto projectionMethodMappings =
2611
0
                    operation::getProjectionMethodMappings(
2612
0
                        nProjectionMethodMappings);
2613
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2614
0
                    const auto &mapping = projectionMethodMappings[i];
2615
0
                    if (metadata::Identifier::isEquivalentName(
2616
0
                            mapping.wkt2_name, methodName.c_str())) {
2617
0
                        methodMapping = &mapping;
2618
0
                    }
2619
0
                }
2620
0
                if (methodMapping == nullptr ||
2621
0
                    methodMapping->proj_name_main == nullptr) {
2622
0
                    throw FactoryException("Cannot insert projection with "
2623
0
                                           "method without identifier");
2624
0
                }
2625
0
                methodAuthName = "PROJ";
2626
0
                methodCode = methodMapping->proj_name_main;
2627
0
                if (methodMapping->proj_name_aux) {
2628
0
                    methodCode += ' ';
2629
0
                    methodCode += methodMapping->proj_name_aux;
2630
0
                }
2631
0
            }
2632
0
        } else {
2633
0
            const auto &methodId = methodIds.front();
2634
0
            methodAuthName = *(methodId->codeSpace());
2635
0
            methodCode = methodId->code();
2636
0
        }
2637
2638
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2639
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2640
0
                                   convAuthName.c_str(), convCode.c_str(),
2641
0
                                   conversion->nameStr().c_str(),
2642
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2643
0
                                   method->nameStr().c_str());
2644
0
        const auto &srcValues = conversion->parameterValues();
2645
0
        if (srcValues.size() > N_MAX_PARAMS) {
2646
0
            throw FactoryException("Cannot insert projection with more than " +
2647
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2648
0
                                   " parameters");
2649
0
        }
2650
2651
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2652
0
        if (methodMapping == nullptr) {
2653
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2654
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2655
0
            } else {
2656
0
                methodMapping =
2657
0
                    operation::getMapping(method->nameStr().c_str());
2658
0
            }
2659
0
        }
2660
0
        if (methodMapping != nullptr) {
2661
            // Re-order projection parameters in their reference order
2662
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2663
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2664
0
                    auto opParamValue = dynamic_cast<
2665
0
                        const operation::OperationParameterValue *>(
2666
0
                        srcValues[i].get());
2667
0
                    if (!opParamValue) {
2668
0
                        throw FactoryException("Cannot insert projection with "
2669
0
                                               "non-OperationParameterValue");
2670
0
                    }
2671
0
                    if (methodMapping->params[j]->wkt2_name &&
2672
0
                        opParamValue->parameter()->nameStr() ==
2673
0
                            methodMapping->params[j]->wkt2_name) {
2674
0
                        values.emplace_back(srcValues[i]);
2675
0
                    }
2676
0
                }
2677
0
            }
2678
0
        }
2679
0
        if (values.size() != srcValues.size()) {
2680
0
            values = srcValues;
2681
0
        }
2682
2683
0
        for (const auto &genOpParamvalue : values) {
2684
0
            auto opParamValue =
2685
0
                dynamic_cast<const operation::OperationParameterValue *>(
2686
0
                    genOpParamvalue.get());
2687
0
            if (!opParamValue) {
2688
0
                throw FactoryException("Cannot insert projection with "
2689
0
                                       "non-OperationParameterValue");
2690
0
            }
2691
0
            const auto &param = opParamValue->parameter();
2692
0
            const auto &paramIds = param->identifiers();
2693
0
            std::string paramAuthName;
2694
0
            std::string paramCode;
2695
0
            if (paramIds.empty()) {
2696
0
                const int paramEPSGCode = param->getEPSGCode();
2697
0
                if (paramEPSGCode == 0) {
2698
0
                    throw FactoryException(
2699
0
                        "Cannot insert projection with method parameter "
2700
0
                        "without identifier");
2701
0
                }
2702
0
                paramAuthName = metadata::Identifier::EPSG;
2703
0
                paramCode = toString(paramEPSGCode);
2704
0
            } else {
2705
0
                const auto &paramId = paramIds.front();
2706
0
                paramAuthName = *(paramId->codeSpace());
2707
0
                paramCode = paramId->code();
2708
0
            }
2709
0
            const auto &value = opParamValue->parameterValue()->value();
2710
0
            const auto &unit = value.unit();
2711
0
            std::string uomAuthName;
2712
0
            std::string uomCode;
2713
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2714
0
                             sqlStatements);
2715
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2716
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2717
0
                                   param->nameStr().c_str(), value.value(),
2718
0
                                   uomAuthName.c_str(), uomCode.c_str());
2719
0
        }
2720
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2721
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2722
0
        }
2723
0
        sql += ",0);";
2724
0
        appendSql(sqlStatements, sql);
2725
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2726
0
                               allowedAuthorities, sqlStatements);
2727
0
    }
2728
2729
    // Find or insert coordinate system
2730
0
    const auto &coordinateSystem = crs->coordinateSystem();
2731
0
    std::string csAuthName;
2732
0
    std::string csCode;
2733
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2734
0
                     csAuthName, csCode, sqlStatements);
2735
2736
    // Insert new record in projected_crs table
2737
0
    const auto sql = formatStatement(
2738
0
        "INSERT INTO projected_crs VALUES("
2739
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2740
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2741
0
        "", // description
2742
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2743
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2744
0
    appendSql(sqlStatements, sql);
2745
2746
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2747
0
                           allowedAuthorities, sqlStatements);
2748
2749
0
    return sqlStatements;
2750
0
}
2751
2752
// ---------------------------------------------------------------------------
2753
2754
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2755
    const datum::VerticalReferenceFrameNNPtr &datum,
2756
    const std::string &authName, const std::string &code,
2757
    bool /* numericCode */,
2758
0
    const std::vector<std::string> &allowedAuthorities) {
2759
2760
0
    const auto self = NN_NO_CHECK(self_.lock());
2761
2762
0
    std::vector<std::string> sqlStatements;
2763
2764
    // Check if the object is already known under that code
2765
0
    std::string datumAuthName;
2766
0
    std::string datumCode;
2767
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2768
0
                           datumAuthName, datumCode);
2769
0
    if (datumAuthName == authName && datumCode == code) {
2770
0
        return {};
2771
0
    }
2772
2773
    // Insert new record in vertical_datum table
2774
0
    std::string publicationDate("NULL");
2775
0
    if (datum->publicationDate().has_value()) {
2776
0
        publicationDate = '\'';
2777
0
        publicationDate +=
2778
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2779
0
        publicationDate += '\'';
2780
0
    }
2781
0
    std::string frameReferenceEpoch("NULL");
2782
0
    const auto dynamicDatum =
2783
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2784
0
    if (dynamicDatum) {
2785
0
        frameReferenceEpoch =
2786
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2787
0
    }
2788
0
    const std::string anchor(*(datum->anchorDefinition()));
2789
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2790
0
    const auto sql = formatStatement(
2791
0
        "INSERT INTO vertical_datum VALUES("
2792
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2793
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2794
0
        "", // description
2795
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2796
0
        anchor.empty() ? nullptr : anchor.c_str(),
2797
0
        anchorEpoch.has_value()
2798
0
            ? anchorEpochToStr(
2799
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2800
0
                  .c_str()
2801
0
            : "NULL");
2802
0
    appendSql(sqlStatements, sql);
2803
2804
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2805
0
                           allowedAuthorities, sqlStatements);
2806
2807
0
    return sqlStatements;
2808
0
}
2809
2810
// ---------------------------------------------------------------------------
2811
2812
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2813
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2814
    const std::string &code, bool numericCode,
2815
0
    const std::vector<std::string> &allowedAuthorities) {
2816
2817
0
    const auto self = NN_NO_CHECK(self_.lock());
2818
2819
0
    std::vector<std::string> sqlStatements;
2820
2821
    // Find or insert datum/datum ensemble
2822
0
    std::string datumAuthName;
2823
0
    std::string datumCode;
2824
0
    const auto &ensemble = crs->datumEnsemble();
2825
0
    if (ensemble) {
2826
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2827
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2828
0
                               datumAuthName, datumCode);
2829
0
        if (datumAuthName.empty()) {
2830
0
            datumAuthName = authName;
2831
0
            if (numericCode) {
2832
0
                datumCode =
2833
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2834
0
            } else {
2835
0
                datumCode = "VERTICAL_DATUM_" + code;
2836
0
            }
2837
0
            sqlStatements = self->getInsertStatementsFor(
2838
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2839
0
                allowedAuthorities);
2840
0
        }
2841
0
    } else {
2842
0
        const auto &datum = crs->datum();
2843
0
        assert(datum);
2844
0
        const auto datumNN = NN_NO_CHECK(datum);
2845
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2846
0
                               datumAuthName, datumCode);
2847
0
        if (datumAuthName.empty()) {
2848
0
            datumAuthName = authName;
2849
0
            if (numericCode) {
2850
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2851
0
            } else {
2852
0
                datumCode = "VERTICAL_DATUM_" + code;
2853
0
            }
2854
0
            sqlStatements =
2855
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2856
0
                                             numericCode, allowedAuthorities);
2857
0
        }
2858
0
    }
2859
2860
    // Find or insert coordinate system
2861
0
    const auto &coordinateSystem = crs->coordinateSystem();
2862
0
    std::string csAuthName;
2863
0
    std::string csCode;
2864
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2865
0
                     csAuthName, csCode, sqlStatements);
2866
2867
    // Insert new record in vertical_crs table
2868
0
    const auto sql =
2869
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2870
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2871
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2872
0
                        "", // description
2873
0
                        csAuthName.c_str(), csCode.c_str(),
2874
0
                        datumAuthName.c_str(), datumCode.c_str());
2875
0
    appendSql(sqlStatements, sql);
2876
2877
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2878
0
                           allowedAuthorities, sqlStatements);
2879
2880
0
    return sqlStatements;
2881
0
}
2882
2883
// ---------------------------------------------------------------------------
2884
2885
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2886
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2887
    const std::string &code, bool numericCode,
2888
0
    const std::vector<std::string> &allowedAuthorities) {
2889
2890
0
    const auto self = NN_NO_CHECK(self_.lock());
2891
2892
0
    std::vector<std::string> sqlStatements;
2893
2894
0
    int counter = 1;
2895
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2896
0
    const auto &components = crs->componentReferenceSystems();
2897
0
    if (components.size() != 2) {
2898
0
        throw FactoryException(
2899
0
            "Cannot insert compound CRS with number of components != 2");
2900
0
    }
2901
2902
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2903
0
    allowedAuthoritiesTmp.emplace_back(authName);
2904
2905
0
    for (const auto &component : components) {
2906
0
        std::string compAuthName;
2907
0
        std::string compCode;
2908
2909
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2910
0
            const auto factory =
2911
0
                AuthorityFactory::create(self, allowedAuthority);
2912
0
            const auto candidates = component->identify(factory);
2913
0
            for (const auto &candidate : candidates) {
2914
0
                if (candidate.second == 100) {
2915
0
                    const auto &ids = candidate.first->identifiers();
2916
0
                    if (!ids.empty()) {
2917
0
                        const auto &id = ids.front();
2918
0
                        compAuthName = *(id->codeSpace());
2919
0
                        compCode = id->code();
2920
0
                        break;
2921
0
                    }
2922
0
                }
2923
0
                if (!compAuthName.empty()) {
2924
0
                    break;
2925
0
                }
2926
0
            }
2927
0
        }
2928
2929
0
        if (compAuthName.empty()) {
2930
0
            compAuthName = authName;
2931
0
            if (numericCode) {
2932
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2933
0
            } else {
2934
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2935
0
            }
2936
0
            const auto sqlStatementsTmp =
2937
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2938
0
                                             numericCode, allowedAuthorities);
2939
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2940
0
                                 sqlStatementsTmp.end());
2941
0
        }
2942
2943
0
        componentsId.emplace_back(
2944
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2945
2946
0
        ++counter;
2947
0
    }
2948
2949
    // Insert new record in compound_crs table
2950
0
    const auto sql = formatStatement(
2951
0
        "INSERT INTO compound_crs VALUES("
2952
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2953
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2954
0
        "", // description
2955
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2956
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2957
0
    appendSql(sqlStatements, sql);
2958
2959
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2960
0
                           allowedAuthorities, sqlStatements);
2961
2962
0
    return sqlStatements;
2963
0
}
2964
2965
//! @endcond
2966
2967
// ---------------------------------------------------------------------------
2968
2969
//! @cond Doxygen_Suppress
2970
10.7k
DatabaseContext::~DatabaseContext() {
2971
10.7k
    try {
2972
10.7k
        stopInsertStatementsSession();
2973
10.7k
    } catch (const std::exception &) {
2974
0
    }
2975
10.7k
}
2976
//! @endcond
2977
2978
// ---------------------------------------------------------------------------
2979
2980
10.7k
DatabaseContext::DatabaseContext() : d(std::make_unique<Private>()) {}
2981
2982
// ---------------------------------------------------------------------------
2983
2984
/** \brief Instantiate a database context.
2985
 *
2986
 * This database context should be used only by one thread at a time.
2987
 *
2988
 * @param databasePath Path and filename of the database. Might be empty
2989
 * string for the default rules to locate the default proj.db
2990
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2991
 * Might be empty.
2992
 * Starting with PROJ 8.1, if this parameter is an empty array,
2993
 * the PROJ_AUX_DB environment variable will be used, if set.
2994
 * It must contain one or several paths. If several paths are
2995
 * provided, they must be separated by the colon (:) character on Unix, and
2996
 * on Windows, by the semi-colon (;) character.
2997
 * @param ctx Context used for file search.
2998
 * @throw FactoryException if the database cannot be opened.
2999
 */
3000
DatabaseContextNNPtr
3001
DatabaseContext::create(const std::string &databasePath,
3002
                        const std::vector<std::string> &auxiliaryDatabasePaths,
3003
10.7k
                        PJ_CONTEXT *ctx) {
3004
10.7k
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
3005
10.7k
    auto dbCtxPrivate = dbCtx->getPrivate();
3006
10.7k
    dbCtxPrivate->open(databasePath, ctx);
3007
10.7k
    auto auxDbs(auxiliaryDatabasePaths);
3008
10.7k
    if (auxDbs.empty()) {
3009
10.7k
        const char *auxDbStr = getenv("PROJ_AUX_DB");
3010
10.7k
        if (auxDbStr) {
3011
#ifdef _WIN32
3012
            const char *delim = ";";
3013
#else
3014
0
            const char *delim = ":";
3015
0
#endif
3016
0
            auxDbs = split(auxDbStr, delim);
3017
0
        }
3018
10.7k
    }
3019
10.7k
    if (!auxDbs.empty()) {
3020
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
3021
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
3022
0
    }
3023
10.7k
    dbCtxPrivate->self_ = dbCtx.as_nullable();
3024
10.7k
    return dbCtx;
3025
10.7k
}
3026
3027
// ---------------------------------------------------------------------------
3028
3029
/** \brief Return the list of authorities used in the database.
3030
 */
3031
129
std::set<std::string> DatabaseContext::getAuthorities() const {
3032
129
    auto res = d->run("SELECT auth_name FROM authority_list");
3033
129
    std::set<std::string> list;
3034
1.03k
    for (const auto &row : res) {
3035
1.03k
        list.insert(row[0]);
3036
1.03k
    }
3037
129
    return list;
3038
129
}
3039
3040
// ---------------------------------------------------------------------------
3041
3042
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
3043
 * CREATE VIEW) needed to initialize a new database.
3044
 */
3045
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
3046
0
    return d->getDatabaseStructure();
3047
0
}
3048
3049
// ---------------------------------------------------------------------------
3050
3051
/** \brief Return the path to the database.
3052
 */
3053
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
3054
3055
// ---------------------------------------------------------------------------
3056
3057
/** \brief Return a metadata item.
3058
 *
3059
 * Value remains valid while this is alive and to the next call to getMetadata
3060
 */
3061
0
const char *DatabaseContext::getMetadata(const char *key) const {
3062
0
    auto res =
3063
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
3064
0
    if (res.empty()) {
3065
0
        return nullptr;
3066
0
    }
3067
0
    d->lastMetadataValue_ = res.front()[0];
3068
0
    return d->lastMetadataValue_.c_str();
3069
0
}
3070
3071
// ---------------------------------------------------------------------------
3072
3073
/** \brief Starts a session for getInsertStatementsFor()
3074
 *
3075
 * Starts a new session for one or several calls to getInsertStatementsFor().
3076
 * An insertion session guarantees that the inserted objects will not create
3077
 * conflicting intermediate objects.
3078
 *
3079
 * The session must be stopped with stopInsertStatementsSession().
3080
 *
3081
 * Only one session may be active at a time for a given database context.
3082
 *
3083
 * @throw FactoryException in case of error.
3084
 * @since 8.1
3085
 */
3086
0
void DatabaseContext::startInsertStatementsSession() {
3087
0
    if (d->memoryDbHandle_) {
3088
0
        throw FactoryException(
3089
0
            "startInsertStatementsSession() cannot be invoked until "
3090
0
            "stopInsertStatementsSession() is.");
3091
0
    }
3092
3093
0
    d->memoryDbForInsertPath_.clear();
3094
0
    const auto sqlStatements = getDatabaseStructure();
3095
3096
    // Create a in-memory temporary sqlite3 database
3097
0
    std::ostringstream buffer;
3098
0
    buffer << "file:temp_db_for_insert_statements_";
3099
0
    buffer << this;
3100
0
    buffer << ".db?mode=memory&cache=shared";
3101
0
    d->memoryDbForInsertPath_ = buffer.str();
3102
0
    sqlite3 *memoryDbHandle = nullptr;
3103
0
    sqlite3_open_v2(
3104
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3105
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3106
0
    if (memoryDbHandle == nullptr) {
3107
0
        throw FactoryException("Cannot create in-memory database");
3108
0
    }
3109
0
    d->memoryDbHandle_ =
3110
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3111
3112
    // Fill the structure of this database
3113
0
    for (const auto &sql : sqlStatements) {
3114
0
        char *errmsg = nullptr;
3115
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3116
0
                         nullptr, &errmsg) != SQLITE_OK) {
3117
0
            const auto sErrMsg =
3118
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3119
0
            sqlite3_free(errmsg);
3120
0
            throw FactoryException(sErrMsg);
3121
0
        }
3122
0
        sqlite3_free(errmsg);
3123
0
    }
3124
3125
    // Attach this database to the current one(s)
3126
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3127
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3128
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3129
0
}
3130
3131
// ---------------------------------------------------------------------------
3132
3133
/** \brief Suggests a database code for the passed object.
3134
 *
3135
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3136
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3137
 *
3138
 * @param object Object for which to suggest a code.
3139
 * @param authName Authority name into which the object will be inserted.
3140
 * @param numericCode Whether the code should be numeric, or derived from the
3141
 * object name.
3142
 * @return the suggested code, that is guaranteed to not conflict with an
3143
 * existing one.
3144
 *
3145
 * @throw FactoryException in case of error.
3146
 * @since 8.1
3147
 */
3148
std::string
3149
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3150
                                 const std::string &authName,
3151
0
                                 bool numericCode) {
3152
0
    const char *tableName = "prime_meridian";
3153
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3154
        // tableName = "prime_meridian";
3155
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3156
0
        tableName = "ellipsoid";
3157
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3158
0
                   object.get())) {
3159
0
        tableName = "geodetic_datum";
3160
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3161
0
                   object.get())) {
3162
0
        tableName = "vertical_datum";
3163
0
    } else if (const auto ensemble =
3164
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3165
0
        const auto &datums = ensemble->datums();
3166
0
        if (!datums.empty() &&
3167
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3168
0
                datums[0].get())) {
3169
0
            tableName = "geodetic_datum";
3170
0
        } else {
3171
0
            tableName = "vertical_datum";
3172
0
        }
3173
0
    } else if (const auto boundCRS =
3174
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3175
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3176
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3177
0
        tableName = "crs_view";
3178
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3179
0
        tableName = "conversion";
3180
0
    } else {
3181
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3182
0
    }
3183
3184
0
    if (numericCode) {
3185
0
        std::string sql("SELECT MAX(code) FROM ");
3186
0
        sql += tableName;
3187
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3188
0
               "AND upper(code) = lower(code)";
3189
0
        const auto res = d->run(sql, {authName});
3190
0
        if (res.empty()) {
3191
0
            return "1";
3192
0
        }
3193
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3194
0
    }
3195
3196
0
    std::string code;
3197
0
    code.reserve(object->nameStr().size());
3198
0
    bool insertUnderscore = false;
3199
0
    for (const auto ch : toupper(object->nameStr())) {
3200
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3201
0
            if (insertUnderscore && code.back() != '_')
3202
0
                code += '_';
3203
0
            code += ch;
3204
0
            insertUnderscore = false;
3205
0
        } else {
3206
0
            insertUnderscore = true;
3207
0
        }
3208
0
    }
3209
0
    return d->findFreeCode(tableName, authName, code);
3210
0
}
3211
3212
// ---------------------------------------------------------------------------
3213
3214
/** \brief Returns SQL statements needed to insert the passed object into the
3215
 * database.
3216
 *
3217
 * startInsertStatementsSession() must have been called previously.
3218
 *
3219
 * @param object The object to insert into the database. Currently only
3220
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3221
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3222
 * @param authName Authority name into which the object will be inserted.
3223
 * @param code Code with which the object will be inserted.
3224
 * @param numericCode Whether intermediate objects that can be created should
3225
 *                    use numeric codes (true), or may be alphanumeric (false)
3226
 * @param allowedAuthorities Authorities to which intermediate objects are
3227
 *                           allowed to refer to. authName will be implicitly
3228
 *                           added to it. Note that unit, coordinate
3229
 *                           systems, projection methods and parameters will in
3230
 *                           any case be allowed to refer to EPSG.
3231
 * @throw FactoryException in case of error.
3232
 * @since 8.1
3233
 */
3234
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3235
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3236
    const std::string &code, bool numericCode,
3237
0
    const std::vector<std::string> &allowedAuthorities) {
3238
0
    if (d->memoryDbHandle_ == nullptr) {
3239
0
        throw FactoryException(
3240
0
            "startInsertStatementsSession() should be invoked first");
3241
0
    }
3242
3243
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3244
0
    if (crs) {
3245
        // Check if the object is already known under that code
3246
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3247
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3248
0
        allowedAuthoritiesTmp.emplace_back(authName);
3249
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3250
0
            const auto factory =
3251
0
                AuthorityFactory::create(self, allowedAuthority);
3252
0
            const auto candidates = crs->identify(factory);
3253
0
            for (const auto &candidate : candidates) {
3254
0
                if (candidate.second == 100) {
3255
0
                    const auto &ids = candidate.first->identifiers();
3256
0
                    for (const auto &id : ids) {
3257
0
                        if (*(id->codeSpace()) == authName &&
3258
0
                            id->code() == code) {
3259
0
                            return {};
3260
0
                        }
3261
0
                    }
3262
0
                }
3263
0
            }
3264
0
        }
3265
0
    }
3266
3267
0
    if (const auto pm =
3268
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3269
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3270
0
                                         numericCode, allowedAuthorities);
3271
0
    }
3272
3273
0
    else if (const auto ellipsoid =
3274
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3275
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3276
0
                                         numericCode, allowedAuthorities);
3277
0
    }
3278
3279
0
    else if (const auto geodeticDatum =
3280
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3281
0
                     object)) {
3282
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3283
0
                                         code, numericCode, allowedAuthorities);
3284
0
    }
3285
3286
0
    else if (const auto ensemble =
3287
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3288
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3289
0
                                         numericCode, allowedAuthorities);
3290
0
    }
3291
3292
0
    else if (const auto geodCRS =
3293
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3294
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3295
0
                                         numericCode, allowedAuthorities);
3296
0
    }
3297
3298
0
    else if (const auto projCRS =
3299
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3300
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3301
0
                                         numericCode, allowedAuthorities);
3302
0
    }
3303
3304
0
    else if (const auto verticalDatum =
3305
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3306
0
                     object)) {
3307
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3308
0
                                         code, numericCode, allowedAuthorities);
3309
0
    }
3310
3311
0
    else if (const auto vertCRS =
3312
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3313
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3314
0
                                         numericCode, allowedAuthorities);
3315
0
    }
3316
3317
0
    else if (const auto compoundCRS =
3318
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3319
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3320
0
                                         code, numericCode, allowedAuthorities);
3321
0
    }
3322
3323
0
    else if (const auto boundCRS =
3324
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3325
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3326
0
                                      numericCode, allowedAuthorities);
3327
0
    }
3328
3329
0
    else {
3330
0
        throw FactoryException(
3331
0
            "getInsertStatementsFor(): unhandled type of object");
3332
0
    }
3333
0
}
3334
3335
// ---------------------------------------------------------------------------
3336
3337
/** \brief Stops an insertion session started with
3338
 * startInsertStatementsSession()
3339
 *
3340
 * @since 8.1
3341
 */
3342
10.7k
void DatabaseContext::stopInsertStatementsSession() {
3343
10.7k
    if (d->memoryDbHandle_) {
3344
0
        d->clearCaches();
3345
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3346
0
        d->memoryDbHandle_.reset();
3347
0
        d->memoryDbForInsertPath_.clear();
3348
0
    }
3349
10.7k
}
3350
3351
// ---------------------------------------------------------------------------
3352
3353
//! @cond Doxygen_Suppress
3354
3355
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3356
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3357
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3358
0
    return ctxt;
3359
0
}
3360
3361
// ---------------------------------------------------------------------------
3362
3363
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3364
3365
// ---------------------------------------------------------------------------
3366
3367
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3368
                                             std::string &projFilename,
3369
                                             std::string &projFormat,
3370
104k
                                             bool &inverse) const {
3371
104k
    auto res = d->run(
3372
104k
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3373
104k
        "grid_alternatives WHERE original_grid_name = ? AND "
3374
104k
        "proj_grid_name <> ''",
3375
104k
        {officialName});
3376
104k
    if (res.empty()) {
3377
1.72k
        return false;
3378
1.72k
    }
3379
102k
    const auto &row = res.front();
3380
102k
    projFilename = row[0];
3381
102k
    projFormat = row[1];
3382
102k
    inverse = row[2] == "1";
3383
102k
    return true;
3384
104k
}
3385
3386
// ---------------------------------------------------------------------------
3387
3388
static std::string makeCachedGridKey(const std::string &projFilename,
3389
                                     bool networkEnabled,
3390
176k
                                     bool considerKnownGridsAsAvailable) {
3391
176k
    std::string key(projFilename);
3392
176k
    key += networkEnabled ? "true" : "false";
3393
176k
    key += considerKnownGridsAsAvailable ? "true" : "false";
3394
176k
    return key;
3395
176k
}
3396
3397
// ---------------------------------------------------------------------------
3398
3399
/** Invalidates information related to projFilename that might have been
3400
 * previously cached by lookForGridInfo().
3401
 *
3402
 * This is useful when downloading a new grid during a session.
3403
 */
3404
0
void DatabaseContext::invalidateGridInfo(const std::string &projFilename) {
3405
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false));
3406
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true));
3407
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false));
3408
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true));
3409
0
}
3410
3411
// ---------------------------------------------------------------------------
3412
3413
bool DatabaseContext::lookForGridInfo(
3414
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3415
    std::string &fullFilename, std::string &packageName, std::string &url,
3416
177k
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3417
177k
    Private::GridInfoCache info;
3418
3419
177k
    if (projFilename == "null") {
3420
        // Special case for implicit "null" grid.
3421
685
        fullFilename.clear();
3422
685
        packageName.clear();
3423
685
        url.clear();
3424
685
        directDownload = false;
3425
685
        openLicense = true;
3426
685
        gridAvailable = true;
3427
685
        return true;
3428
685
    }
3429
3430
176k
    auto ctxt = d->pjCtxt();
3431
176k
    if (ctxt == nullptr) {
3432
0
        ctxt = pj_get_default_ctx();
3433
0
        d->setPjCtxt(ctxt);
3434
0
    }
3435
3436
176k
    const std::string key(makeCachedGridKey(
3437
176k
        projFilename, proj_context_is_network_enabled(ctxt) != false,
3438
176k
        considerKnownGridsAsAvailable));
3439
176k
    if (d->getGridInfoFromCache(key, info)) {
3440
136k
        fullFilename = info.fullFilename;
3441
136k
        packageName = info.packageName;
3442
136k
        url = info.url;
3443
136k
        directDownload = info.directDownload;
3444
136k
        openLicense = info.openLicense;
3445
136k
        gridAvailable = info.gridAvailable;
3446
136k
        return info.found;
3447
136k
    }
3448
3449
40.1k
    fullFilename.clear();
3450
40.1k
    packageName.clear();
3451
40.1k
    url.clear();
3452
40.1k
    openLicense = false;
3453
40.1k
    directDownload = false;
3454
40.1k
    gridAvailable = false;
3455
3456
40.1k
    const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() {
3457
40.1k
        fullFilename.resize(2048);
3458
40.1k
        const int errno_before = proj_context_errno(ctxt);
3459
40.1k
        bool lGridAvailable = NS_PROJ::FileManager::open_resource_file(
3460
40.1k
                                  ctxt, projFilename.c_str(), &fullFilename[0],
3461
40.1k
                                  fullFilename.size() - 1) != nullptr;
3462
40.1k
        proj_context_errno_set(ctxt, errno_before);
3463
40.1k
        fullFilename.resize(strlen(fullFilename.c_str()));
3464
40.1k
        return lGridAvailable;
3465
40.1k
    };
3466
3467
40.1k
    auto res =
3468
40.1k
        d->run("SELECT "
3469
40.1k
               "grid_packages.package_name, "
3470
40.1k
               "grid_alternatives.url, "
3471
40.1k
               "grid_packages.url AS package_url, "
3472
40.1k
               "grid_alternatives.open_license, "
3473
40.1k
               "grid_packages.open_license AS package_open_license, "
3474
40.1k
               "grid_alternatives.direct_download, "
3475
40.1k
               "grid_packages.direct_download AS package_direct_download, "
3476
40.1k
               "grid_alternatives.proj_grid_name, "
3477
40.1k
               "grid_alternatives.old_proj_grid_name "
3478
40.1k
               "FROM grid_alternatives "
3479
40.1k
               "LEFT JOIN grid_packages ON "
3480
40.1k
               "grid_alternatives.package_name = grid_packages.package_name "
3481
40.1k
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3482
40.1k
               {projFilename, projFilename});
3483
40.1k
    bool ret = !res.empty();
3484
40.1k
    if (ret) {
3485
28.0k
        const auto &row = res.front();
3486
28.0k
        packageName = std::move(row[0]);
3487
28.0k
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3488
28.0k
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3489
28.0k
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3490
3491
28.0k
        const auto &proj_grid_name = row[7];
3492
28.0k
        const auto &old_proj_grid_name = row[8];
3493
28.0k
        if (proj_grid_name != old_proj_grid_name &&
3494
28.0k
            old_proj_grid_name == projFilename) {
3495
91
            std::string fullFilenameNewName;
3496
91
            fullFilenameNewName.resize(2048);
3497
91
            const int errno_before = proj_context_errno(ctxt);
3498
91
            bool gridAvailableWithNewName =
3499
91
                pj_find_file(ctxt, proj_grid_name.c_str(),
3500
91
                             &fullFilenameNewName[0],
3501
91
                             fullFilenameNewName.size() - 1) != 0;
3502
91
            proj_context_errno_set(ctxt, errno_before);
3503
91
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3504
91
            if (gridAvailableWithNewName) {
3505
0
                gridAvailable = true;
3506
0
                fullFilename = std::move(fullFilenameNewName);
3507
0
            }
3508
91
        }
3509
3510
28.0k
        if (!gridAvailable && considerKnownGridsAsAvailable &&
3511
64
            (!packageName.empty() || (!url.empty() && openLicense))) {
3512
3513
            // In considerKnownGridsAsAvailable mode, try to fetch the local
3514
            // file name if it exists, but do not attempt network access.
3515
4
            const auto network_was_enabled =
3516
4
                proj_context_is_network_enabled(ctxt);
3517
4
            proj_context_set_enable_network(ctxt, false);
3518
4
            (void)resolveFullFilename();
3519
4
            proj_context_set_enable_network(ctxt, network_was_enabled);
3520
3521
4
            gridAvailable = true;
3522
4
        }
3523
3524
28.0k
        info.packageName = packageName;
3525
28.0k
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3526
28.0k
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3527
27.8k
            if (endpoint.back() != '/') {
3528
27.8k
                endpoint += '/';
3529
27.8k
            }
3530
27.8k
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3531
27.8k
        }
3532
28.0k
        info.directDownload = directDownload;
3533
28.0k
        info.openLicense = openLicense;
3534
3535
28.0k
        if (!gridAvailable) {
3536
28.0k
            gridAvailable = resolveFullFilename();
3537
28.0k
        }
3538
28.0k
    } else {
3539
12.0k
        gridAvailable = resolveFullFilename();
3540
3541
12.0k
        if (starts_with(fullFilename, "http://") ||
3542
12.0k
            starts_with(fullFilename, "https://")) {
3543
0
            url = fullFilename;
3544
0
            fullFilename.clear();
3545
0
        }
3546
12.0k
    }
3547
3548
40.1k
    info.fullFilename = fullFilename;
3549
40.1k
    info.url = url;
3550
40.1k
    info.gridAvailable = gridAvailable;
3551
40.1k
    info.found = ret;
3552
40.1k
    d->cache(key, info);
3553
40.1k
    return ret;
3554
176k
}
3555
3556
// ---------------------------------------------------------------------------
3557
3558
/** Returns the number of queries to the database since the creation of this
3559
 * instance.
3560
 */
3561
0
unsigned int DatabaseContext::getQueryCounter() const {
3562
0
    return d->queryCounter_;
3563
0
}
3564
3565
// ---------------------------------------------------------------------------
3566
3567
bool DatabaseContext::isKnownName(const std::string &name,
3568
0
                                  const std::string &tableName) const {
3569
0
    std::string sql("SELECT 1 FROM \"");
3570
0
    sql += replaceAll(tableName, "\"", "\"\"");
3571
0
    sql += "\" WHERE name = ? LIMIT 1";
3572
0
    return !d->run(sql, {name}).empty();
3573
0
}
3574
// ---------------------------------------------------------------------------
3575
3576
std::string
3577
32.1k
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3578
32.1k
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3579
32.1k
                      "old_proj_grid_name = ?",
3580
32.1k
                      {oldProjGridName});
3581
32.1k
    if (res.empty()) {
3582
31.5k
        return std::string();
3583
31.5k
    }
3584
560
    return res.front()[0];
3585
32.1k
}
3586
3587
// ---------------------------------------------------------------------------
3588
3589
28.3k
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3590
28.3k
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3591
28.3k
                      "proj_grid_name = ?",
3592
28.3k
                      {gridName});
3593
28.3k
    if (res.empty()) {
3594
792
        return std::string();
3595
792
    }
3596
27.5k
    return res.front()[0];
3597
28.3k
}
3598
3599
// ---------------------------------------------------------------------------
3600
3601
// scripts/build_db_from_esri.py adds a second alias for
3602
// names that have '[' in them. See get_old_esri_name()
3603
// in scripts/build_db_from_esri.py
3604
// So if we only have two aliases detect that situation to get the official
3605
// new name
3606
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3607
0
    std::string first = l.front();
3608
0
    std::string second = *(std::next(l.begin()));
3609
0
    if (second.find('[') != std::string::npos)
3610
0
        std::swap(first, second);
3611
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3612
0
        second) {
3613
0
        return first;
3614
0
    }
3615
0
    return std::string();
3616
0
}
3617
3618
// ---------------------------------------------------------------------------
3619
3620
/** \brief Gets the alias name from an official name.
3621
 *
3622
 * @param officialName Official name. Mandatory
3623
 * @param tableName Table name/category. Mandatory.
3624
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3625
 *                  accepted as special names to add a constraint on the "type"
3626
 *                  column of the "geodetic_crs" table.
3627
 * @param source Source of the alias. Mandatory
3628
 * @return Alias name (or empty if not found).
3629
 * @throw FactoryException in case of error.
3630
 */
3631
std::string
3632
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3633
                                          const std::string &tableName,
3634
0
                                          const std::string &source) const {
3635
0
    std::string sql("SELECT auth_name, code FROM \"");
3636
0
    const auto genuineTableName =
3637
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3638
0
            ? "geodetic_crs"
3639
0
            : tableName;
3640
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3641
0
    sql += "\" WHERE name = ?";
3642
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3643
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3644
0
    } else if (tableName == "geographic_3D_crs") {
3645
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3646
0
    }
3647
0
    sql += " ORDER BY deprecated";
3648
0
    auto res = d->run(sql, {officialName});
3649
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3650
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3651
    // NAD83(HARN), and that's definitely not desirable.
3652
0
    if (res.empty() &&
3653
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3654
0
        res = d->run(
3655
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3656
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3657
0
            {genuineTableName, officialName});
3658
0
        if (res.size() != 1) {
3659
0
            return std::string();
3660
0
        }
3661
0
    }
3662
0
    for (const auto &row : res) {
3663
0
        auto res2 =
3664
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3665
0
                   "auth_name = ? AND code = ? AND source = ?",
3666
0
                   {genuineTableName, row[0], row[1], source});
3667
0
        if (!res2.empty()) {
3668
0
            if (res2.size() == 2 && source == "ESRI") {
3669
0
                std::list<std::string> l;
3670
0
                l.emplace_back(res2.front()[0]);
3671
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3672
0
                std::string uniqueEsriAlias = getUniqueEsriAlias(l);
3673
0
                if (!uniqueEsriAlias.empty())
3674
0
                    return uniqueEsriAlias;
3675
0
            }
3676
0
            return res2.front()[0];
3677
0
        }
3678
0
    }
3679
0
    return std::string();
3680
0
}
3681
3682
// ---------------------------------------------------------------------------
3683
3684
/** \brief Gets the alias names for an object.
3685
 *
3686
 * Either authName + code or officialName must be non empty.
3687
 *
3688
 * @param authName Authority.
3689
 * @param code Code.
3690
 * @param officialName Official name.
3691
 * @param tableName Table name/category. Mandatory.
3692
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3693
 *                  accepted as special names to add a constraint on the "type"
3694
 *                  column of the "geodetic_crs" table.
3695
 * @param source Source of the alias. May be empty.
3696
 * @return Aliases
3697
 */
3698
std::list<std::string> DatabaseContext::getAliases(
3699
    const std::string &authName, const std::string &code,
3700
    const std::string &officialName, const std::string &tableName,
3701
519k
    const std::string &source) const {
3702
3703
519k
    std::list<std::string> res;
3704
519k
    const auto key(authName + code + officialName + tableName + source);
3705
519k
    if (d->cacheAliasNames_.tryGet(key, res)) {
3706
509k
        return res;
3707
509k
    }
3708
3709
9.48k
    std::string resolvedAuthName(authName);
3710
9.48k
    std::string resolvedCode(code);
3711
9.48k
    const auto genuineTableName =
3712
9.48k
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3713
9.48k
            ? "geodetic_crs"
3714
9.48k
            : tableName;
3715
9.48k
    if (authName.empty() || code.empty()) {
3716
5.58k
        std::string sql("SELECT auth_name, code FROM \"");
3717
5.58k
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3718
5.58k
        sql += "\" WHERE name = ?";
3719
5.58k
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3720
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3721
5.58k
        } else if (tableName == "geographic_3D_crs") {
3722
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3723
0
        }
3724
5.58k
        sql += " ORDER BY deprecated";
3725
5.58k
        auto resSql = d->run(sql, {officialName});
3726
5.58k
        if (resSql.empty()) {
3727
3.60k
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3728
3.60k
                            "table_name = ? AND "
3729
3.60k
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3730
3.60k
                            {genuineTableName, officialName});
3731
3.60k
            if (resSql.size() != 1) {
3732
1.86k
                d->cacheAliasNames_.insert(key, res);
3733
1.86k
                return res;
3734
1.86k
            }
3735
3.60k
        }
3736
3.72k
        const auto &row = resSql.front();
3737
3.72k
        resolvedAuthName = row[0];
3738
3.72k
        resolvedCode = row[1];
3739
3.72k
    }
3740
7.62k
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3741
7.62k
                    "auth_name = ? AND code = ?");
3742
7.62k
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3743
7.62k
    if (source == "not EPSG_OLD") {
3744
41
        sql += " AND source != 'EPSG_OLD'";
3745
7.58k
    } else if (!source.empty()) {
3746
0
        sql += " AND source = ?";
3747
0
        params.emplace_back(source);
3748
0
    }
3749
7.62k
    auto resSql = d->run(sql, params);
3750
28.7k
    for (const auto &row : resSql) {
3751
28.7k
        res.emplace_back(row[0]);
3752
28.7k
    }
3753
3754
7.62k
    if (res.size() == 2 && source == "ESRI") {
3755
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3756
0
        if (!uniqueEsriAlias.empty()) {
3757
0
            res.clear();
3758
0
            res.emplace_back(uniqueEsriAlias);
3759
0
        }
3760
0
    }
3761
3762
7.62k
    d->cacheAliasNames_.insert(key, res);
3763
7.62k
    return res;
3764
9.48k
}
3765
3766
// ---------------------------------------------------------------------------
3767
3768
/** \brief Return the 'name' column of a table for an object
3769
 *
3770
 * @param tableName Table name/category.
3771
 * @param authName Authority name of the object.
3772
 * @param code Code of the object
3773
 * @return Name (or empty)
3774
 * @throw FactoryException in case of error.
3775
 */
3776
std::string DatabaseContext::getName(const std::string &tableName,
3777
                                     const std::string &authName,
3778
251k
                                     const std::string &code) const {
3779
251k
    std::string res;
3780
251k
    const auto key(tableName + authName + code);
3781
251k
    if (d->cacheNames_.tryGet(key, res)) {
3782
248k
        return res;
3783
248k
    }
3784
3785
3.66k
    std::string sql("SELECT name FROM \"");
3786
3.66k
    sql += replaceAll(tableName, "\"", "\"\"");
3787
3.66k
    sql += "\" WHERE auth_name = ? AND code = ?";
3788
3.66k
    auto sqlRes = d->run(sql, {authName, code});
3789
3.66k
    if (sqlRes.empty()) {
3790
1
        res.clear();
3791
3.66k
    } else {
3792
3.66k
        res = sqlRes.front()[0];
3793
3.66k
    }
3794
3.66k
    d->cacheNames_.insert(key, res);
3795
3.66k
    return res;
3796
251k
}
3797
3798
// ---------------------------------------------------------------------------
3799
3800
/** \brief Return the 'text_definition' column of a table for an object
3801
 *
3802
 * @param tableName Table name/category.
3803
 * @param authName Authority name of the object.
3804
 * @param code Code of the object
3805
 * @return Text definition (or empty)
3806
 * @throw FactoryException in case of error.
3807
 */
3808
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3809
                                               const std::string &authName,
3810
0
                                               const std::string &code) const {
3811
0
    std::string sql("SELECT text_definition FROM \"");
3812
0
    sql += replaceAll(tableName, "\"", "\"\"");
3813
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3814
0
    auto res = d->run(sql, {authName, code});
3815
0
    if (res.empty()) {
3816
0
        return std::string();
3817
0
    }
3818
0
    return res.front()[0];
3819
0
}
3820
3821
// ---------------------------------------------------------------------------
3822
3823
/** \brief Return the allowed authorities when researching transformations
3824
 * between different authorities.
3825
 *
3826
 * @throw FactoryException in case of error.
3827
 */
3828
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3829
    const std::string &sourceAuthName,
3830
148k
    const std::string &targetAuthName) const {
3831
3832
148k
    const auto key(sourceAuthName + targetAuthName);
3833
148k
    auto hit = d->cacheAllowedAuthorities_.find(key);
3834
148k
    if (hit != d->cacheAllowedAuthorities_.end()) {
3835
144k
        return hit->second;
3836
144k
    }
3837
3838
3.15k
    auto sqlRes = d->run(
3839
3.15k
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3840
3.15k
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3841
3.15k
        {sourceAuthName, targetAuthName});
3842
3.15k
    if (sqlRes.empty()) {
3843
977
        sqlRes = d->run(
3844
977
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3845
977
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3846
977
            {sourceAuthName});
3847
977
    }
3848
3.15k
    if (sqlRes.empty()) {
3849
977
        sqlRes = d->run(
3850
977
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3851
977
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3852
977
            {targetAuthName});
3853
977
    }
3854
3.15k
    if (sqlRes.empty()) {
3855
597
        sqlRes = d->run(
3856
597
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3857
597
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3858
597
            {});
3859
597
    }
3860
3.15k
    if (sqlRes.empty()) {
3861
597
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3862
597
        return std::vector<std::string>();
3863
597
    }
3864
2.55k
    auto res = split(sqlRes.front()[0], ',');
3865
2.55k
    d->cacheAllowedAuthorities_[key] = res;
3866
2.55k
    return res;
3867
3.15k
}
3868
3869
// ---------------------------------------------------------------------------
3870
3871
std::list<std::pair<std::string, std::string>>
3872
DatabaseContext::getNonDeprecated(const std::string &tableName,
3873
                                  const std::string &authName,
3874
0
                                  const std::string &code) const {
3875
0
    auto sqlRes =
3876
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3877
0
               "FROM deprecation "
3878
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3879
0
               "AND deprecated_code = ?",
3880
0
               {tableName, authName, code});
3881
0
    std::list<std::pair<std::string, std::string>> res;
3882
0
    for (const auto &row : sqlRes) {
3883
0
        const auto &source = row[2];
3884
0
        if (source == "PROJ") {
3885
0
            const auto &replacement_auth_name = row[0];
3886
0
            const auto &replacement_code = row[1];
3887
0
            res.emplace_back(replacement_auth_name, replacement_code);
3888
0
        }
3889
0
    }
3890
0
    if (!res.empty()) {
3891
0
        return res;
3892
0
    }
3893
0
    for (const auto &row : sqlRes) {
3894
0
        const auto &replacement_auth_name = row[0];
3895
0
        const auto &replacement_code = row[1];
3896
0
        res.emplace_back(replacement_auth_name, replacement_code);
3897
0
    }
3898
0
    return res;
3899
0
}
3900
3901
// ---------------------------------------------------------------------------
3902
3903
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3904
943
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3905
943
    if (cacheAuthNameWithVersion_.empty()) {
3906
213
        const auto sqlRes =
3907
213
            run("SELECT versioned_auth_name, auth_name, version, priority "
3908
213
                "FROM versioned_auth_name_mapping");
3909
213
        for (const auto &row : sqlRes) {
3910
213
            VersionedAuthName van;
3911
213
            van.versionedAuthName = row[0];
3912
213
            van.authName = row[1];
3913
213
            van.version = row[2];
3914
213
            van.priority = atoi(row[3].c_str());
3915
213
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3916
213
        }
3917
213
    }
3918
943
    return cacheAuthNameWithVersion_;
3919
943
}
3920
3921
// ---------------------------------------------------------------------------
3922
3923
// From IAU_2015 returns (IAU,2015)
3924
bool DatabaseContext::getAuthorityAndVersion(
3925
    const std::string &versionedAuthName, std::string &authNameOut,
3926
0
    std::string &versionOut) {
3927
3928
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3929
0
        if (van.versionedAuthName == versionedAuthName) {
3930
0
            authNameOut = van.authName;
3931
0
            versionOut = van.version;
3932
0
            return true;
3933
0
        }
3934
0
    }
3935
0
    return false;
3936
0
}
3937
3938
// ---------------------------------------------------------------------------
3939
3940
// From IAU and 2015, returns IAU_2015
3941
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3942
                                            const std::string &version,
3943
802
                                            std::string &versionedAuthNameOut) {
3944
3945
802
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3946
802
        if (van.authName == authName && van.version == version) {
3947
0
            versionedAuthNameOut = van.versionedAuthName;
3948
0
            return true;
3949
0
        }
3950
802
    }
3951
802
    return false;
3952
802
}
3953
3954
// ---------------------------------------------------------------------------
3955
3956
// From IAU returns IAU_latest, ... IAU_2015
3957
std::vector<std::string>
3958
141
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3959
3960
141
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3961
141
    std::vector<VersionedAuthNamePriority> tmp;
3962
141
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3963
141
        if (van.authName == authName) {
3964
0
            tmp.emplace_back(
3965
0
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3966
0
        }
3967
141
    }
3968
141
    std::vector<std::string> res;
3969
141
    if (!tmp.empty()) {
3970
        // Sort by decreasing priority
3971
0
        std::sort(tmp.begin(), tmp.end(),
3972
0
                  [](const VersionedAuthNamePriority &a,
3973
0
                     const VersionedAuthNamePriority &b) {
3974
0
                      return b.second > a.second;
3975
0
                  });
3976
0
        for (const auto &pair : tmp)
3977
0
            res.emplace_back(pair.first);
3978
0
    }
3979
141
    return res;
3980
141
}
3981
3982
// ---------------------------------------------------------------------------
3983
3984
std::vector<operation::CoordinateOperationNNPtr>
3985
DatabaseContext::getTransformationsForGridName(
3986
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3987
0
    auto sqlRes = databaseContext->d->run(
3988
0
        "SELECT auth_name, code FROM grid_transformation "
3989
0
        "WHERE grid_name = ? OR grid_name IN "
3990
0
        "(SELECT original_grid_name FROM grid_alternatives "
3991
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3992
0
        {gridName, gridName});
3993
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3994
0
    for (const auto &row : sqlRes) {
3995
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3996
0
                             ->createCoordinateOperation(row[1], true));
3997
0
    }
3998
0
    return res;
3999
0
}
4000
4001
// ---------------------------------------------------------------------------
4002
4003
// Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame
4004
// transformation, where they neglect to reverse the sign of the rotation terms.
4005
// Cf https://github.com/OSGeo/PROJ/issues/4170 and
4006
// https://github.com/maptiler/epsg.io/issues/194
4007
// We do that only when we found a valid Coordinate Frame rotation that
4008
// has the same numeric values (and no corresponding Position Vector
4009
// transformation with same values, or Coordinate Frame transformation with
4010
// opposite sign for rotation terms, both are highly unlikely)
4011
bool DatabaseContext::toWGS84AutocorrectWrongValues(
4012
    double &tx, double &ty, double &tz, double &rx, double &ry, double &rz,
4013
6
    double &scale_difference) const {
4014
6
    if (rx == 0 && ry == 0 && rz == 0)
4015
4
        return false;
4016
    // 9606: Position Vector transformation (geog2D domain)
4017
    // 9607: Coordinate Frame rotation (geog2D domain)
4018
2
    std::string sql(
4019
2
        "SELECT DISTINCT method_code "
4020
2
        "FROM helmert_transformation_table WHERE "
4021
2
        "abs(tx - ?) <= 1e-8 * abs(tx) AND "
4022
2
        "abs(ty - ?) <= 1e-8 * abs(ty) AND "
4023
2
        "abs(tz - ?) <= 1e-8 * abs(tz) AND "
4024
2
        "abs(rx - ?) <= 1e-8 * abs(rx) AND "
4025
2
        "abs(ry - ?) <= 1e-8 * abs(ry) AND "
4026
2
        "abs(rz - ?) <= 1e-8 * abs(rz) AND "
4027
2
        "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND "
4028
2
        "method_auth_name = 'EPSG' AND "
4029
2
        "method_code IN (9606, 9607) AND "
4030
2
        "translation_uom_auth_name = 'EPSG' AND "
4031
2
        "translation_uom_code = 9001 AND " // metre
4032
2
        "rotation_uom_auth_name = 'EPSG' AND "
4033
2
        "rotation_uom_code = 9104 AND " // arc-second
4034
2
        "scale_difference_uom_auth_name = 'EPSG' AND "
4035
2
        "scale_difference_uom_code = 9202 AND " // parts per million
4036
2
        "deprecated = 0");
4037
2
    ListOfParams params;
4038
2
    params.emplace_back(tx);
4039
2
    params.emplace_back(ty);
4040
2
    params.emplace_back(tz);
4041
2
    params.emplace_back(rx);
4042
2
    params.emplace_back(ry);
4043
2
    params.emplace_back(rz);
4044
2
    params.emplace_back(scale_difference);
4045
2
    bool bFound9606 = false;
4046
2
    bool bFound9607 = false;
4047
2
    for (const auto &row : d->run(sql, params)) {
4048
0
        if (row[0] == "9606") {
4049
0
            bFound9606 = true;
4050
0
        } else if (row[0] == "9607") {
4051
0
            bFound9607 = true;
4052
0
        }
4053
0
    }
4054
2
    if (bFound9607 && !bFound9606) {
4055
0
        params.clear();
4056
0
        params.emplace_back(tx);
4057
0
        params.emplace_back(ty);
4058
0
        params.emplace_back(tz);
4059
0
        params.emplace_back(-rx);
4060
0
        params.emplace_back(-ry);
4061
0
        params.emplace_back(-rz);
4062
0
        params.emplace_back(scale_difference);
4063
0
        if (d->run(sql, params).empty()) {
4064
0
            if (d->pjCtxt()) {
4065
0
                pj_log(d->pjCtxt(), PJ_LOG_ERROR,
4066
0
                       "Auto-correcting wrong sign of rotation terms of "
4067
0
                       "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to "
4068
0
                       "%s,%s,%s,%s,%s,%s,%s",
4069
0
                       internal::toString(tx).c_str(),
4070
0
                       internal::toString(ty).c_str(),
4071
0
                       internal::toString(tz).c_str(),
4072
0
                       internal::toString(rx).c_str(),
4073
0
                       internal::toString(ry).c_str(),
4074
0
                       internal::toString(rz).c_str(),
4075
0
                       internal::toString(scale_difference).c_str(),
4076
0
                       internal::toString(tx).c_str(),
4077
0
                       internal::toString(ty).c_str(),
4078
0
                       internal::toString(tz).c_str(),
4079
0
                       internal::toString(-rx).c_str(),
4080
0
                       internal::toString(-ry).c_str(),
4081
0
                       internal::toString(-rz).c_str(),
4082
0
                       internal::toString(scale_difference).c_str());
4083
0
            }
4084
0
            rx = -rx;
4085
0
            ry = -ry;
4086
0
            rz = -rz;
4087
0
            return true;
4088
0
        }
4089
0
    }
4090
2
    return false;
4091
2
}
4092
4093
//! @endcond
4094
4095
// ---------------------------------------------------------------------------
4096
4097
//! @cond Doxygen_Suppress
4098
struct AuthorityFactory::Private {
4099
    Private(const DatabaseContextNNPtr &contextIn,
4100
            const std::string &authorityName)
4101
1.52M
        : context_(contextIn), authority_(authorityName) {}
4102
4103
3.77M
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
4104
4105
5.74M
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
4106
5.74M
        return context_;
4107
5.74M
    }
4108
4109
    // cppcheck-suppress functionStatic
4110
1.52M
    void setThis(AuthorityFactoryNNPtr factory) {
4111
1.52M
        thisFactory_ = factory.as_nullable();
4112
1.52M
    }
4113
4114
    // cppcheck-suppress functionStatic
4115
25.2k
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
4116
4117
1.34M
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
4118
1.34M
        if (auth_name == authority_) {
4119
921k
            return NN_NO_CHECK(thisFactory_.lock());
4120
921k
        }
4121
423k
        return AuthorityFactory::create(context_, auth_name);
4122
1.34M
    }
4123
4124
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
4125
                                  bool considerKnownGridsAsAvailable);
4126
4127
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
4128
                                      const std::string &code);
4129
4130
    util::PropertyMap
4131
    createProperties(const std::string &code, const std::string &name,
4132
                     bool deprecated,
4133
                     const std::vector<ObjectDomainNNPtr> &usages);
4134
4135
    util::PropertyMap
4136
    createPropertiesSearchUsages(const std::string &table_name,
4137
                                 const std::string &code,
4138
                                 const std::string &name, bool deprecated);
4139
4140
    util::PropertyMap createPropertiesSearchUsages(
4141
        const std::string &table_name, const std::string &code,
4142
        const std::string &name, bool deprecated, const std::string &remarks);
4143
4144
    SQLResultSet run(const std::string &sql,
4145
                     const ListOfParams &parameters = ListOfParams());
4146
4147
    SQLResultSet runWithCodeParam(const std::string &sql,
4148
                                  const std::string &code);
4149
4150
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
4151
4152
345k
    bool hasAuthorityRestriction() const {
4153
345k
        return !authority_.empty() && authority_ != "any";
4154
345k
    }
4155
4156
    SQLResultSet createProjectedCRSBegin(const std::string &code);
4157
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
4158
                                                 const SQLResultSet &res);
4159
4160
  private:
4161
    DatabaseContextNNPtr context_;
4162
    std::string authority_;
4163
    std::weak_ptr<AuthorityFactory> thisFactory_{};
4164
};
4165
4166
// ---------------------------------------------------------------------------
4167
4168
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
4169
1.21M
                                            const ListOfParams &parameters) {
4170
1.21M
    return context()->getPrivate()->run(sql, parameters);
4171
1.21M
}
4172
4173
// ---------------------------------------------------------------------------
4174
4175
SQLResultSet
4176
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
4177
467k
                                            const std::string &code) {
4178
467k
    return run(sql, {authority(), code});
4179
467k
}
4180
4181
// ---------------------------------------------------------------------------
4182
4183
SQLResultSet
4184
AuthorityFactory::Private::runWithCodeParam(const char *sql,
4185
413k
                                            const std::string &code) {
4186
413k
    return runWithCodeParam(std::string(sql), code);
4187
413k
}
4188
4189
// ---------------------------------------------------------------------------
4190
4191
UnitOfMeasure
4192
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
4193
207k
                                               const std::string &code) {
4194
207k
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
4195
207k
}
4196
4197
// ---------------------------------------------------------------------------
4198
4199
util::PropertyMap AuthorityFactory::Private::createProperties(
4200
    const std::string &code, const std::string &name, bool deprecated,
4201
311k
    const std::vector<ObjectDomainNNPtr> &usages) {
4202
311k
    auto props = util::PropertyMap()
4203
311k
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
4204
311k
                     .set(metadata::Identifier::CODE_KEY, code)
4205
311k
                     .set(common::IdentifiedObject::NAME_KEY, name);
4206
311k
    if (deprecated) {
4207
245
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
4208
245
    }
4209
311k
    if (!usages.empty()) {
4210
4211
297k
        auto array(util::ArrayOfBaseObject::create());
4212
297k
        for (const auto &usage : usages) {
4213
297k
            array->add(usage);
4214
297k
        }
4215
297k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
4216
297k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
4217
297k
    }
4218
311k
    return props;
4219
311k
}
4220
4221
// ---------------------------------------------------------------------------
4222
4223
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4224
    const std::string &table_name, const std::string &code,
4225
298k
    const std::string &name, bool deprecated) {
4226
4227
298k
    SQLResultSet res;
4228
298k
    if (table_name == "geodetic_crs" && code == "4326" &&
4229
3.34k
        authority() == "EPSG") {
4230
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
4231
        // description is super verbose.
4232
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
4233
        // To avoid churn in our WKT2 output, hot patch to the usage of
4234
        // 10.076 and earlier
4235
3.34k
        res = run("SELECT extent.description, extent.south_lat, "
4236
3.34k
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
4237
3.34k
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
4238
3.34k
                  "extent.code = 1262 and scope.code = 1183");
4239
295k
    } else {
4240
295k
        const std::string sql(
4241
295k
            "SELECT extent.description, extent.south_lat, "
4242
295k
            "extent.north_lat, extent.west_lon, extent.east_lon, "
4243
295k
            "scope.scope, "
4244
295k
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
4245
295k
            "AS score "
4246
295k
            "FROM usage "
4247
295k
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
4248
295k
            "usage.extent_code = extent.code "
4249
295k
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
4250
295k
            "usage.scope_code = scope.code "
4251
295k
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
4252
295k
            "object_code = ? AND "
4253
            // We voluntary exclude extent and scope with a specific code
4254
295k
            "NOT (usage.extent_auth_name = 'PROJ' AND "
4255
295k
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
4256
295k
            "NOT (usage.scope_auth_name = 'PROJ' AND "
4257
295k
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4258
295k
            "ORDER BY score, usage.auth_name, usage.code");
4259
295k
        res = run(sql, {table_name, authority(), code});
4260
295k
    }
4261
298k
    std::vector<ObjectDomainNNPtr> usages;
4262
298k
    for (const auto &row : res) {
4263
297k
        try {
4264
297k
            size_t idx = 0;
4265
297k
            const auto &extent_description = row[idx++];
4266
297k
            const auto &south_lat_str = row[idx++];
4267
297k
            const auto &north_lat_str = row[idx++];
4268
297k
            const auto &west_lon_str = row[idx++];
4269
297k
            const auto &east_lon_str = row[idx++];
4270
297k
            const auto &scope = row[idx];
4271
4272
297k
            util::optional<std::string> scopeOpt;
4273
297k
            if (!scope.empty()) {
4274
297k
                scopeOpt = scope;
4275
297k
            }
4276
4277
297k
            metadata::ExtentPtr extent;
4278
297k
            if (south_lat_str.empty()) {
4279
0
                extent = metadata::Extent::create(
4280
0
                             util::optional<std::string>(extent_description),
4281
0
                             {}, {}, {})
4282
0
                             .as_nullable();
4283
297k
            } else {
4284
297k
                double south_lat = c_locale_stod(south_lat_str);
4285
297k
                double north_lat = c_locale_stod(north_lat_str);
4286
297k
                double west_lon = c_locale_stod(west_lon_str);
4287
297k
                double east_lon = c_locale_stod(east_lon_str);
4288
297k
                auto bbox = metadata::GeographicBoundingBox::create(
4289
297k
                    west_lon, south_lat, east_lon, north_lat);
4290
297k
                extent = metadata::Extent::create(
4291
297k
                             util::optional<std::string>(extent_description),
4292
297k
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4293
297k
                             std::vector<metadata::VerticalExtentNNPtr>(),
4294
297k
                             std::vector<metadata::TemporalExtentNNPtr>())
4295
297k
                             .as_nullable();
4296
297k
            }
4297
4298
297k
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4299
297k
        } catch (const std::exception &) {
4300
0
        }
4301
297k
    }
4302
298k
    return createProperties(code, name, deprecated, std::move(usages));
4303
298k
}
4304
4305
// ---------------------------------------------------------------------------
4306
4307
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4308
    const std::string &table_name, const std::string &code,
4309
241k
    const std::string &name, bool deprecated, const std::string &remarks) {
4310
241k
    auto props =
4311
241k
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4312
241k
    if (!remarks.empty())
4313
197k
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4314
241k
    return props;
4315
241k
}
4316
4317
// ---------------------------------------------------------------------------
4318
4319
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4320
    const operation::CoordinateOperationNNPtr &op,
4321
71.9k
    bool considerKnownGridsAsAvailable) {
4322
4323
71.9k
    struct DisableNetwork {
4324
71.9k
        const DatabaseContextNNPtr &m_dbContext;
4325
71.9k
        bool m_old_network_enabled = false;
4326
4327
71.9k
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4328
71.9k
            : m_dbContext(l_context) {
4329
71.9k
            auto ctxt = m_dbContext->d->pjCtxt();
4330
71.9k
            if (ctxt == nullptr) {
4331
0
                ctxt = pj_get_default_ctx();
4332
0
                m_dbContext->d->setPjCtxt(ctxt);
4333
0
            }
4334
71.9k
            m_old_network_enabled =
4335
71.9k
                proj_context_is_network_enabled(ctxt) != FALSE;
4336
71.9k
            if (m_old_network_enabled)
4337
0
                proj_context_set_enable_network(ctxt, false);
4338
71.9k
        }
4339
4340
71.9k
        ~DisableNetwork() {
4341
71.9k
            if (m_old_network_enabled) {
4342
0
                auto ctxt = m_dbContext->d->pjCtxt();
4343
0
                proj_context_set_enable_network(ctxt, true);
4344
0
            }
4345
71.9k
        }
4346
71.9k
    };
4347
4348
71.9k
    auto &l_context = context();
4349
    // Temporarily disable networking as we are only interested in known grids
4350
71.9k
    DisableNetwork disabler(l_context);
4351
4352
71.9k
    for (const auto &gridDesc :
4353
71.9k
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4354
51.6k
        if (!gridDesc.available) {
4355
51.6k
            return true;
4356
51.6k
        }
4357
51.6k
    }
4358
20.2k
    return false;
4359
71.9k
}
4360
4361
//! @endcond
4362
4363
// ---------------------------------------------------------------------------
4364
4365
//! @cond Doxygen_Suppress
4366
1.52M
AuthorityFactory::~AuthorityFactory() = default;
4367
//! @endcond
4368
4369
// ---------------------------------------------------------------------------
4370
4371
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4372
                                   const std::string &authorityName)
4373
1.52M
    : d(std::make_unique<Private>(context, authorityName)) {}
4374
4375
// ---------------------------------------------------------------------------
4376
4377
// clang-format off
4378
/** \brief Instantiate a AuthorityFactory.
4379
 *
4380
 * The authority name might be set to the empty string in the particular case
4381
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4382
 * is called.
4383
 *
4384
 * @param context Context.
4385
 * @param authorityName Authority name.
4386
 * @return new AuthorityFactory.
4387
 */
4388
// clang-format on
4389
4390
AuthorityFactoryNNPtr
4391
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4392
1.52M
                         const std::string &authorityName) {
4393
1.52M
    const auto getFactory = [&context, &authorityName]() {
4394
1.52M
        for (const auto &knownName :
4395
2.48M
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4396
2.48M
            if (ci_equal(authorityName, knownName)) {
4397
1.13M
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4398
1.13M
                    context, knownName);
4399
1.13M
            }
4400
2.48M
        }
4401
393k
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4402
393k
            context, authorityName);
4403
1.52M
    };
4404
1.52M
    auto factory = getFactory();
4405
1.52M
    factory->d->setThis(factory);
4406
1.52M
    return factory;
4407
1.52M
}
4408
4409
// ---------------------------------------------------------------------------
4410
4411
/** \brief Returns the database context. */
4412
1.42M
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4413
1.42M
    return d->context();
4414
1.42M
}
4415
4416
// ---------------------------------------------------------------------------
4417
4418
//! @cond Doxygen_Suppress
4419
AuthorityFactory::CRSInfo::CRSInfo()
4420
0
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4421
0
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4422
0
      north_lat_degree{}, areaName{}, projectionMethodName{},
4423
0
      celestialBodyName{} {}
4424
//! @endcond
4425
4426
// ---------------------------------------------------------------------------
4427
4428
/** \brief Returns an arbitrary object from a code.
4429
 *
4430
 * The returned object will typically be an instance of Datum,
4431
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4432
 * the object is know at compile time, it is recommended to invoke the most
4433
 * precise method instead of this one (for example
4434
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4435
 * if the caller know he is asking for a coordinate reference system).
4436
 *
4437
 * If there are several objects with the same code, a FactoryException is
4438
 * thrown.
4439
 *
4440
 * @param code Object code allocated by authority. (e.g. "4326")
4441
 * @return object.
4442
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4443
 * @throw FactoryException in case of other errors.
4444
 */
4445
4446
util::BaseObjectNNPtr
4447
0
AuthorityFactory::createObject(const std::string &code) const {
4448
4449
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4450
0
                                   "WHERE auth_name = ? AND code = ?",
4451
0
                                   code);
4452
0
    if (res.empty()) {
4453
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4454
0
    }
4455
0
    if (res.size() != 1) {
4456
0
        std::string msg(
4457
0
            "More than one object matching specified code. Objects found in ");
4458
0
        bool first = true;
4459
0
        for (const auto &row : res) {
4460
0
            if (!first)
4461
0
                msg += ", ";
4462
0
            msg += row[0];
4463
0
            first = false;
4464
0
        }
4465
0
        throw FactoryException(msg);
4466
0
    }
4467
0
    const auto &first_row = res.front();
4468
0
    const auto &table_name = first_row[0];
4469
0
    const auto &type = first_row[1];
4470
0
    if (table_name == "extent") {
4471
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4472
0
            createExtent(code));
4473
0
    }
4474
0
    if (table_name == "unit_of_measure") {
4475
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4476
0
            createUnitOfMeasure(code));
4477
0
    }
4478
0
    if (table_name == "prime_meridian") {
4479
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4480
0
            createPrimeMeridian(code));
4481
0
    }
4482
0
    if (table_name == "ellipsoid") {
4483
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4484
0
            createEllipsoid(code));
4485
0
    }
4486
0
    if (table_name == "geodetic_datum") {
4487
0
        if (type == "ensemble") {
4488
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4489
0
                createDatumEnsemble(code, table_name));
4490
0
        }
4491
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4492
0
            createGeodeticDatum(code));
4493
0
    }
4494
0
    if (table_name == "vertical_datum") {
4495
0
        if (type == "ensemble") {
4496
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4497
0
                createDatumEnsemble(code, table_name));
4498
0
        }
4499
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4500
0
            createVerticalDatum(code));
4501
0
    }
4502
0
    if (table_name == "engineering_datum") {
4503
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4504
0
            createEngineeringDatum(code));
4505
0
    }
4506
0
    if (table_name == "geodetic_crs") {
4507
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4508
0
            createGeodeticCRS(code));
4509
0
    }
4510
0
    if (table_name == "vertical_crs") {
4511
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4512
0
            createVerticalCRS(code));
4513
0
    }
4514
0
    if (table_name == "projected_crs") {
4515
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4516
0
            createProjectedCRS(code));
4517
0
    }
4518
0
    if (table_name == "compound_crs") {
4519
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4520
0
            createCompoundCRS(code));
4521
0
    }
4522
0
    if (table_name == "engineering_crs") {
4523
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4524
0
            createEngineeringCRS(code));
4525
0
    }
4526
0
    if (table_name == "conversion") {
4527
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4528
0
            createConversion(code));
4529
0
    }
4530
0
    if (table_name == "helmert_transformation" ||
4531
0
        table_name == "grid_transformation" ||
4532
0
        table_name == "other_transformation" ||
4533
0
        table_name == "concatenated_operation") {
4534
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4535
0
            createCoordinateOperation(code, false));
4536
0
    }
4537
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4538
0
}
4539
4540
// ---------------------------------------------------------------------------
4541
4542
//! @cond Doxygen_Suppress
4543
static FactoryException buildFactoryException(const char *type,
4544
                                              const std::string &authName,
4545
                                              const std::string &code,
4546
0
                                              const std::exception &ex) {
4547
0
    return FactoryException(std::string("cannot build ") + type + " " +
4548
0
                            authName + ":" + code + ": " + ex.what());
4549
0
}
4550
//! @endcond
4551
4552
// ---------------------------------------------------------------------------
4553
4554
/** \brief Returns a metadata::Extent from the specified code.
4555
 *
4556
 * @param code Object code allocated by authority.
4557
 * @return object.
4558
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4559
 * @throw FactoryException in case of other errors.
4560
 */
4561
4562
metadata::ExtentNNPtr
4563
0
AuthorityFactory::createExtent(const std::string &code) const {
4564
0
    const auto cacheKey(d->authority() + code);
4565
0
    {
4566
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4567
0
        if (extent) {
4568
0
            return NN_NO_CHECK(extent);
4569
0
        }
4570
0
    }
4571
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4572
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4573
0
    auto res = d->runWithCodeParam(sql, code);
4574
0
    if (res.empty()) {
4575
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4576
0
                                           code);
4577
0
    }
4578
0
    try {
4579
0
        const auto &row = res.front();
4580
0
        const auto &description = row[0];
4581
0
        if (row[1].empty()) {
4582
0
            auto extent = metadata::Extent::create(
4583
0
                util::optional<std::string>(description), {}, {}, {});
4584
0
            d->context()->d->cache(cacheKey, extent);
4585
0
            return extent;
4586
0
        }
4587
0
        double south_lat = c_locale_stod(row[1]);
4588
0
        double north_lat = c_locale_stod(row[2]);
4589
0
        double west_lon = c_locale_stod(row[3]);
4590
0
        double east_lon = c_locale_stod(row[4]);
4591
0
        auto bbox = metadata::GeographicBoundingBox::create(
4592
0
            west_lon, south_lat, east_lon, north_lat);
4593
4594
0
        auto extent = metadata::Extent::create(
4595
0
            util::optional<std::string>(description),
4596
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4597
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4598
0
            std::vector<metadata::TemporalExtentNNPtr>());
4599
0
        d->context()->d->cache(cacheKey, extent);
4600
0
        return extent;
4601
4602
0
    } catch (const std::exception &ex) {
4603
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4604
0
    }
4605
0
}
4606
4607
// ---------------------------------------------------------------------------
4608
4609
/** \brief Returns a common::UnitOfMeasure from the specified code.
4610
 *
4611
 * @param code Object code allocated by authority.
4612
 * @return object.
4613
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4614
 * @throw FactoryException in case of other errors.
4615
 */
4616
4617
UnitOfMeasureNNPtr
4618
207k
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4619
207k
    const auto cacheKey(d->authority() + code);
4620
207k
    {
4621
207k
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4622
207k
        if (uom) {
4623
188k
            return NN_NO_CHECK(uom);
4624
188k
        }
4625
207k
    }
4626
19.2k
    auto res = d->context()->d->run(
4627
19.2k
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4628
19.2k
        "auth_name = ? AND code = ?",
4629
19.2k
        {d->authority(), code}, true);
4630
19.2k
    if (res.empty()) {
4631
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4632
0
                                           d->authority(), code);
4633
0
    }
4634
19.2k
    try {
4635
19.2k
        const auto &row = res.front();
4636
19.2k
        const auto &name =
4637
19.2k
            (row[0] == "degree (supplier to define representation)")
4638
19.2k
                ? UnitOfMeasure::DEGREE.name()
4639
19.2k
                : row[0];
4640
19.2k
        double conv_factor = (code == "9107" || code == "9108")
4641
19.2k
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4642
19.2k
                                 : c_locale_stod(row[1]);
4643
19.2k
        constexpr double EPS = 1e-10;
4644
19.2k
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4645
19.2k
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4646
8.31k
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4647
8.31k
        }
4648
19.2k
        if (std::fabs(conv_factor -
4649
19.2k
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4650
19.2k
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4651
1.06k
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4652
1.06k
        }
4653
19.2k
        const auto &type_str = row[2];
4654
19.2k
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4655
19.2k
        if (type_str == "length")
4656
5.54k
            unitType = UnitOfMeasure::Type::LINEAR;
4657
13.7k
        else if (type_str == "angle")
4658
10.7k
            unitType = UnitOfMeasure::Type::ANGULAR;
4659
2.92k
        else if (type_str == "scale")
4660
2.54k
            unitType = UnitOfMeasure::Type::SCALE;
4661
378
        else if (type_str == "time")
4662
378
            unitType = UnitOfMeasure::Type::TIME;
4663
19.2k
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4664
19.2k
            name, conv_factor, unitType, d->authority(), code);
4665
19.2k
        d->context()->d->cache(cacheKey, uom);
4666
19.2k
        return uom;
4667
19.2k
    } catch (const std::exception &ex) {
4668
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4669
0
                                    ex);
4670
0
    }
4671
19.2k
}
4672
4673
// ---------------------------------------------------------------------------
4674
4675
//! @cond Doxygen_Suppress
4676
static double normalizeMeasure(const std::string &uom_code,
4677
                               const std::string &value,
4678
28.9k
                               std::string &normalized_uom_code) {
4679
28.9k
    if (uom_code == "9110") // DDD.MMSSsss.....
4680
3.46k
    {
4681
3.46k
        double normalized_value = c_locale_stod(value);
4682
3.46k
        std::ostringstream buffer;
4683
3.46k
        buffer.imbue(std::locale::classic());
4684
3.46k
        constexpr size_t precision = 12;
4685
3.46k
        buffer << std::fixed << std::setprecision(precision)
4686
3.46k
               << normalized_value;
4687
3.46k
        auto formatted = buffer.str();
4688
3.46k
        size_t dotPos = formatted.find('.');
4689
3.46k
        assert(dotPos + 1 + precision == formatted.size());
4690
3.46k
        auto minutes = formatted.substr(dotPos + 1, 2);
4691
3.46k
        auto seconds = formatted.substr(dotPos + 3);
4692
3.46k
        assert(seconds.size() == precision - 2);
4693
3.46k
        normalized_value =
4694
3.46k
            (normalized_value < 0 ? -1.0 : 1.0) *
4695
3.46k
            (std::floor(std::fabs(normalized_value)) +
4696
3.46k
             c_locale_stod(minutes) / 60. +
4697
3.46k
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4698
3.46k
                 3600.);
4699
3.46k
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4700
        /* coverity[overflow_sink] */
4701
3.46k
        return normalized_value;
4702
25.5k
    } else {
4703
25.5k
        normalized_uom_code = uom_code;
4704
25.5k
        return c_locale_stod(value);
4705
25.5k
    }
4706
28.9k
}
4707
//! @endcond
4708
4709
// ---------------------------------------------------------------------------
4710
4711
/** \brief Returns a datum::PrimeMeridian from the specified code.
4712
 *
4713
 * @param code Object code allocated by authority.
4714
 * @return object.
4715
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4716
 * @throw FactoryException in case of other errors.
4717
 */
4718
4719
datum::PrimeMeridianNNPtr
4720
44.4k
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4721
44.4k
    const auto cacheKey(d->authority() + code);
4722
44.4k
    {
4723
44.4k
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4724
44.4k
        if (pm) {
4725
39.8k
            return NN_NO_CHECK(pm);
4726
39.8k
        }
4727
44.4k
    }
4728
4.56k
    auto res = d->runWithCodeParam(
4729
4.56k
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4730
4.56k
        "prime_meridian WHERE "
4731
4.56k
        "auth_name = ? AND code = ?",
4732
4.56k
        code);
4733
4.56k
    if (res.empty()) {
4734
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4735
0
                                           d->authority(), code);
4736
0
    }
4737
4.56k
    try {
4738
4.56k
        const auto &row = res.front();
4739
4.56k
        const auto &name = row[0];
4740
4.56k
        const auto &longitude = row[1];
4741
4.56k
        const auto &uom_auth_name = row[2];
4742
4.56k
        const auto &uom_code = row[3];
4743
4.56k
        const bool deprecated = row[4] == "1";
4744
4745
4.56k
        std::string normalized_uom_code(uom_code);
4746
4.56k
        const double normalized_value =
4747
4.56k
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4748
4749
4.56k
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4750
4.56k
        auto props = d->createProperties(code, name, deprecated, {});
4751
4.56k
        auto pm = datum::PrimeMeridian::create(
4752
4.56k
            props, common::Angle(normalized_value, uom));
4753
4.56k
        d->context()->d->cache(cacheKey, pm);
4754
4.56k
        return pm;
4755
4.56k
    } catch (const std::exception &ex) {
4756
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4757
0
    }
4758
4.56k
}
4759
4760
// ---------------------------------------------------------------------------
4761
4762
/** \brief Identify a celestial body from an approximate radius.
4763
 *
4764
 * @param semi_major_axis Approximate semi-major axis.
4765
 * @param tolerance Relative error allowed.
4766
 * @return celestial body name if one single match found.
4767
 * @throw FactoryException in case of error.
4768
 */
4769
4770
std::string
4771
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4772
1.58k
                                                double tolerance) const {
4773
1.58k
    auto res =
4774
1.58k
        d->run("SELECT DISTINCT name, "
4775
1.58k
               "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error "
4776
1.58k
               "FROM celestial_body WHERE rel_error <= ? "
4777
1.58k
               "ORDER BY rel_error, name",
4778
1.58k
               {semi_major_axis, tolerance});
4779
1.58k
    if (res.empty()) {
4780
1.45k
        throw FactoryException("no match found");
4781
1.45k
    }
4782
125
    constexpr int IDX_NAME = 0;
4783
125
    if (res.size() > 1) {
4784
22
        constexpr int IDX_REL_ERROR = 1;
4785
        // If the first object has a relative error of 0 and the next one
4786
        // a non-zero error, then use the first one.
4787
22
        if (res.front()[IDX_REL_ERROR] == "0" &&
4788
0
            (*std::next(res.begin()))[IDX_REL_ERROR] != "0") {
4789
0
            return res.front()[IDX_NAME];
4790
0
        }
4791
44
        for (const auto &row : res) {
4792
44
            if (row[IDX_NAME] != res.front()[IDX_NAME]) {
4793
16
                throw FactoryException("more than one match found");
4794
16
            }
4795
44
        }
4796
22
    }
4797
109
    return res.front()[IDX_NAME];
4798
125
}
4799
4800
// ---------------------------------------------------------------------------
4801
4802
/** \brief Returns a datum::Ellipsoid from the specified code.
4803
 *
4804
 * @param code Object code allocated by authority.
4805
 * @return object.
4806
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4807
 * @throw FactoryException in case of other errors.
4808
 */
4809
4810
datum::EllipsoidNNPtr
4811
44.6k
AuthorityFactory::createEllipsoid(const std::string &code) const {
4812
44.6k
    const auto cacheKey(d->authority() + code);
4813
44.6k
    {
4814
44.6k
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4815
44.6k
        if (ellps) {
4816
36.5k
            return NN_NO_CHECK(ellps);
4817
36.5k
        }
4818
44.6k
    }
4819
8.05k
    auto res = d->runWithCodeParam(
4820
8.05k
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4821
8.05k
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4822
8.05k
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4823
8.05k
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4824
8.05k
        "ellipsoid JOIN celestial_body "
4825
8.05k
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4826
8.05k
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4827
8.05k
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4828
8.05k
        code);
4829
8.05k
    if (res.empty()) {
4830
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4831
0
                                           d->authority(), code);
4832
0
    }
4833
8.05k
    try {
4834
8.05k
        const auto &row = res.front();
4835
8.05k
        const auto &name = row[0];
4836
8.05k
        const auto &semi_major_axis_str = row[1];
4837
8.05k
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4838
8.05k
        const auto &uom_auth_name = row[2];
4839
8.05k
        const auto &uom_code = row[3];
4840
8.05k
        const auto &inv_flattening_str = row[4];
4841
8.05k
        const auto &semi_minor_axis_str = row[5];
4842
8.05k
        const auto &body = row[6];
4843
8.05k
        const bool deprecated = row[7] == "1";
4844
8.05k
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4845
8.05k
        auto props = d->createProperties(code, name, deprecated, {});
4846
8.05k
        if (!inv_flattening_str.empty()) {
4847
7.11k
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4848
7.11k
                props, common::Length(semi_major_axis, uom),
4849
7.11k
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4850
7.11k
            d->context()->d->cache(cacheKey, ellps);
4851
7.11k
            return ellps;
4852
7.11k
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4853
59
            auto ellps = datum::Ellipsoid::createSphere(
4854
59
                props, common::Length(semi_major_axis, uom), body);
4855
59
            d->context()->d->cache(cacheKey, ellps);
4856
59
            return ellps;
4857
881
        } else {
4858
881
            auto ellps = datum::Ellipsoid::createTwoAxis(
4859
881
                props, common::Length(semi_major_axis, uom),
4860
881
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4861
881
            d->context()->d->cache(cacheKey, ellps);
4862
881
            return ellps;
4863
881
        }
4864
8.05k
    } catch (const std::exception &ex) {
4865
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4866
0
    }
4867
8.05k
}
4868
4869
// ---------------------------------------------------------------------------
4870
4871
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4872
 *
4873
 * @param code Object code allocated by authority.
4874
 * @return object.
4875
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4876
 * @throw FactoryException in case of other errors.
4877
 */
4878
4879
datum::GeodeticReferenceFrameNNPtr
4880
414k
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4881
4882
414k
    datum::GeodeticReferenceFramePtr datum;
4883
414k
    datum::DatumEnsemblePtr datumEnsemble;
4884
414k
    constexpr bool turnEnsembleAsDatum = true;
4885
414k
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4886
414k
                                  turnEnsembleAsDatum);
4887
414k
    return NN_NO_CHECK(datum);
4888
414k
}
4889
4890
// ---------------------------------------------------------------------------
4891
4892
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4893
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4894
464k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4895
464k
    const auto cacheKey(d->authority() + code);
4896
464k
    {
4897
464k
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4898
464k
        if (outDatumEnsemble) {
4899
410k
            if (!turnEnsembleAsDatum)
4900
26.6k
                return;
4901
384k
            outDatumEnsemble = nullptr;
4902
384k
        }
4903
437k
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4904
437k
        if (outDatum) {
4905
389k
            return;
4906
389k
        }
4907
437k
    }
4908
48.1k
    auto res = d->runWithCodeParam(
4909
48.1k
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4910
48.1k
        "prime_meridian_auth_name, prime_meridian_code, "
4911
48.1k
        "publication_date, frame_reference_epoch, "
4912
48.1k
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4913
48.1k
        "FROM geodetic_datum "
4914
48.1k
        "WHERE "
4915
48.1k
        "auth_name = ? AND code = ?",
4916
48.1k
        code);
4917
48.1k
    if (res.empty()) {
4918
28
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4919
28
                                           d->authority(), code);
4920
28
    }
4921
48.0k
    try {
4922
48.0k
        const auto &row = res.front();
4923
48.0k
        const auto &name = row[0];
4924
48.0k
        const auto &ellipsoid_auth_name = row[1];
4925
48.0k
        const auto &ellipsoid_code = row[2];
4926
48.0k
        const auto &prime_meridian_auth_name = row[3];
4927
48.0k
        const auto &prime_meridian_code = row[4];
4928
48.0k
        const auto &publication_date = row[5];
4929
48.0k
        const auto &frame_reference_epoch = row[6];
4930
48.0k
        const auto &ensemble_accuracy = row[7];
4931
48.0k
        const auto &anchor = row[8];
4932
48.0k
        const auto &anchor_epoch = row[9];
4933
48.0k
        const bool deprecated = row[10] == "1";
4934
4935
48.0k
        std::string massagedName;
4936
48.0k
        if (turnEnsembleAsDatum) {
4937
33.1k
            massagedName =
4938
33.1k
                datum::DatumEnsemble::ensembleNameToNonEnsembleName(name);
4939
33.1k
        }
4940
48.0k
        if (massagedName.empty()) {
4941
44.9k
            massagedName = name;
4942
44.9k
        }
4943
48.0k
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4944
48.0k
                                                     massagedName, deprecated);
4945
4946
48.0k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4947
3.67k
            auto resMembers =
4948
3.67k
                d->run("SELECT member_auth_name, member_code FROM "
4949
3.67k
                       "geodetic_datum_ensemble_member WHERE "
4950
3.67k
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4951
3.67k
                       "ORDER BY sequence",
4952
3.67k
                       {d->authority(), code});
4953
4954
3.67k
            std::vector<datum::DatumNNPtr> members;
4955
30.4k
            for (const auto &memberRow : resMembers) {
4956
30.4k
                members.push_back(
4957
30.4k
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4958
30.4k
            }
4959
3.67k
            auto datumEnsemble = datum::DatumEnsemble::create(
4960
3.67k
                props, std::move(members),
4961
3.67k
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4962
3.67k
            d->context()->d->cache(cacheKey, datumEnsemble);
4963
3.67k
            outDatumEnsemble = datumEnsemble.as_nullable();
4964
44.4k
        } else {
4965
44.4k
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4966
44.4k
                                 ->createEllipsoid(ellipsoid_code);
4967
44.4k
            auto pm = d->createFactory(prime_meridian_auth_name)
4968
44.4k
                          ->createPrimeMeridian(prime_meridian_code);
4969
4970
44.4k
            auto anchorOpt = util::optional<std::string>();
4971
44.4k
            if (!anchor.empty())
4972
51
                anchorOpt = anchor;
4973
44.4k
            if (!publication_date.empty()) {
4974
39.6k
                props.set("PUBLICATION_DATE", publication_date);
4975
39.6k
            }
4976
44.4k
            if (!anchor_epoch.empty()) {
4977
2.07k
                props.set("ANCHOR_EPOCH", anchor_epoch);
4978
2.07k
            }
4979
44.4k
            auto datum = frame_reference_epoch.empty()
4980
44.4k
                             ? datum::GeodeticReferenceFrame::create(
4981
12.3k
                                   props, ellipsoid, anchorOpt, pm)
4982
44.4k
                             : util::nn_static_pointer_cast<
4983
32.1k
                                   datum::GeodeticReferenceFrame>(
4984
32.1k
                                   datum::DynamicGeodeticReferenceFrame::create(
4985
32.1k
                                       props, ellipsoid, anchorOpt, pm,
4986
32.1k
                                       common::Measure(
4987
32.1k
                                           c_locale_stod(frame_reference_epoch),
4988
32.1k
                                           common::UnitOfMeasure::YEAR),
4989
32.1k
                                       util::optional<std::string>()));
4990
44.4k
            d->context()->d->cache(cacheKey, datum);
4991
44.4k
            outDatum = datum.as_nullable();
4992
44.4k
        }
4993
48.0k
    } catch (const std::exception &ex) {
4994
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
4995
0
                                    code, ex);
4996
0
    }
4997
48.0k
}
4998
4999
// ---------------------------------------------------------------------------
5000
5001
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
5002
 *
5003
 * @param code Object code allocated by authority.
5004
 * @return object.
5005
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5006
 * @throw FactoryException in case of other errors.
5007
 */
5008
5009
datum::VerticalReferenceFrameNNPtr
5010
94
AuthorityFactory::createVerticalDatum(const std::string &code) const {
5011
94
    datum::VerticalReferenceFramePtr datum;
5012
94
    datum::DatumEnsemblePtr datumEnsemble;
5013
94
    constexpr bool turnEnsembleAsDatum = true;
5014
94
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
5015
94
                                  turnEnsembleAsDatum);
5016
94
    return NN_NO_CHECK(datum);
5017
94
}
5018
5019
// ---------------------------------------------------------------------------
5020
5021
void AuthorityFactory::createVerticalDatumOrEnsemble(
5022
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
5023
1.37k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
5024
1.37k
    auto res =
5025
1.37k
        d->runWithCodeParam("SELECT name, publication_date, "
5026
1.37k
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
5027
1.37k
                            "anchor_epoch, deprecated FROM "
5028
1.37k
                            "vertical_datum WHERE auth_name = ? AND code = ?",
5029
1.37k
                            code);
5030
1.37k
    if (res.empty()) {
5031
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
5032
0
                                           d->authority(), code);
5033
0
    }
5034
1.37k
    try {
5035
1.37k
        const auto &row = res.front();
5036
1.37k
        const auto &name = row[0];
5037
1.37k
        const auto &publication_date = row[1];
5038
1.37k
        const auto &frame_reference_epoch = row[2];
5039
1.37k
        const auto &ensemble_accuracy = row[3];
5040
1.37k
        const auto &anchor = row[4];
5041
1.37k
        const auto &anchor_epoch = row[5];
5042
1.37k
        const bool deprecated = row[6] == "1";
5043
1.37k
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
5044
1.37k
                                                     name, deprecated);
5045
1.37k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
5046
19
            auto resMembers =
5047
19
                d->run("SELECT member_auth_name, member_code FROM "
5048
19
                       "vertical_datum_ensemble_member WHERE "
5049
19
                       "ensemble_auth_name = ? AND ensemble_code = ? "
5050
19
                       "ORDER BY sequence",
5051
19
                       {d->authority(), code});
5052
5053
19
            std::vector<datum::DatumNNPtr> members;
5054
94
            for (const auto &memberRow : resMembers) {
5055
94
                members.push_back(
5056
94
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5057
94
            }
5058
19
            auto datumEnsemble = datum::DatumEnsemble::create(
5059
19
                props, std::move(members),
5060
19
                metadata::PositionalAccuracy::create(ensemble_accuracy));
5061
19
            outDatumEnsemble = datumEnsemble.as_nullable();
5062
1.35k
        } else {
5063
1.35k
            if (!publication_date.empty()) {
5064
629
                props.set("PUBLICATION_DATE", publication_date);
5065
629
            }
5066
1.35k
            if (!anchor_epoch.empty()) {
5067
14
                props.set("ANCHOR_EPOCH", anchor_epoch);
5068
14
            }
5069
1.35k
            if (d->authority() == "ESRI" &&
5070
501
                starts_with(code, "from_geogdatum_")) {
5071
486
                props.set("VERT_DATUM_TYPE", "2002");
5072
486
            }
5073
1.35k
            auto anchorOpt = util::optional<std::string>();
5074
1.35k
            if (!anchor.empty())
5075
0
                anchorOpt = anchor;
5076
1.35k
            if (frame_reference_epoch.empty()) {
5077
1.33k
                outDatum =
5078
1.33k
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
5079
1.33k
                        .as_nullable();
5080
1.33k
            } else {
5081
16
                outDatum =
5082
16
                    datum::DynamicVerticalReferenceFrame::create(
5083
16
                        props, anchorOpt,
5084
16
                        util::optional<datum::RealizationMethod>(),
5085
16
                        common::Measure(c_locale_stod(frame_reference_epoch),
5086
16
                                        common::UnitOfMeasure::YEAR),
5087
16
                        util::optional<std::string>())
5088
16
                        .as_nullable();
5089
16
            }
5090
1.35k
        }
5091
1.37k
    } catch (const std::exception &ex) {
5092
0
        throw buildFactoryException("vertical reference frame", d->authority(),
5093
0
                                    code, ex);
5094
0
    }
5095
1.37k
}
5096
5097
// ---------------------------------------------------------------------------
5098
5099
/** \brief Returns a datum::EngineeringDatum from the specified code.
5100
 *
5101
 * @param code Object code allocated by authority.
5102
 * @return object.
5103
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5104
 * @throw FactoryException in case of other errors.
5105
 * @since 9.6
5106
 */
5107
5108
datum::EngineeringDatumNNPtr
5109
17
AuthorityFactory::createEngineeringDatum(const std::string &code) const {
5110
17
    auto res = d->runWithCodeParam(
5111
17
        "SELECT name, publication_date, "
5112
17
        "anchor, anchor_epoch, deprecated FROM "
5113
17
        "engineering_datum WHERE auth_name = ? AND code = ?",
5114
17
        code);
5115
17
    if (res.empty()) {
5116
0
        throw NoSuchAuthorityCodeException("engineering datum not found",
5117
0
                                           d->authority(), code);
5118
0
    }
5119
17
    try {
5120
17
        const auto &row = res.front();
5121
17
        const auto &name = row[0];
5122
17
        const auto &publication_date = row[1];
5123
17
        const auto &anchor = row[2];
5124
17
        const auto &anchor_epoch = row[3];
5125
17
        const bool deprecated = row[4] == "1";
5126
17
        auto props = d->createPropertiesSearchUsages("engineering_datum", code,
5127
17
                                                     name, deprecated);
5128
5129
17
        if (!publication_date.empty()) {
5130
3
            props.set("PUBLICATION_DATE", publication_date);
5131
3
        }
5132
17
        if (!anchor_epoch.empty()) {
5133
0
            props.set("ANCHOR_EPOCH", anchor_epoch);
5134
0
        }
5135
17
        auto anchorOpt = util::optional<std::string>();
5136
17
        if (!anchor.empty())
5137
0
            anchorOpt = anchor;
5138
17
        return datum::EngineeringDatum::create(props, anchorOpt);
5139
17
    } catch (const std::exception &ex) {
5140
0
        throw buildFactoryException("engineering datum", d->authority(), code,
5141
0
                                    ex);
5142
0
    }
5143
17
}
5144
5145
// ---------------------------------------------------------------------------
5146
5147
/** \brief Returns a datum::DatumEnsemble from the specified code.
5148
 *
5149
 * @param code Object code allocated by authority.
5150
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
5151
 * @return object.
5152
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5153
 * @throw FactoryException in case of other errors.
5154
 */
5155
5156
datum::DatumEnsembleNNPtr
5157
AuthorityFactory::createDatumEnsemble(const std::string &code,
5158
0
                                      const std::string &type) const {
5159
0
    auto res = d->run(
5160
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
5161
0
        "geodetic_datum WHERE "
5162
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
5163
0
        "UNION ALL "
5164
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
5165
0
        "vertical_datum WHERE "
5166
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
5167
0
        {d->authority(), code, d->authority(), code});
5168
0
    if (res.empty()) {
5169
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
5170
0
                                           d->authority(), code);
5171
0
    }
5172
0
    for (const auto &row : res) {
5173
0
        const std::string &gotType = row[0];
5174
0
        const std::string &name = row[1];
5175
0
        const std::string &ensembleAccuracy = row[2];
5176
0
        const bool deprecated = row[3] == "1";
5177
0
        if (type.empty() || type == gotType) {
5178
0
            auto resMembers =
5179
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
5180
0
                           "_ensemble_member WHERE "
5181
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
5182
0
                           "ORDER BY sequence",
5183
0
                       {d->authority(), code});
5184
5185
0
            std::vector<datum::DatumNNPtr> members;
5186
0
            for (const auto &memberRow : resMembers) {
5187
0
                members.push_back(
5188
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5189
0
            }
5190
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
5191
0
                                                         deprecated);
5192
0
            return datum::DatumEnsemble::create(
5193
0
                props, std::move(members),
5194
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
5195
0
        }
5196
0
    }
5197
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
5198
0
                                       d->authority(), code);
5199
0
}
5200
5201
// ---------------------------------------------------------------------------
5202
5203
/** \brief Returns a datum::Datum from the specified code.
5204
 *
5205
 * @param code Object code allocated by authority.
5206
 * @return object.
5207
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5208
 * @throw FactoryException in case of other errors.
5209
 */
5210
5211
30.5k
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
5212
30.5k
    auto res = d->run(
5213
30.5k
        "SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
5214
30.5k
        "auth_name = ? AND code = ? "
5215
30.5k
        "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
5216
30.5k
        "auth_name = ? AND code = ? "
5217
30.5k
        "UNION ALL SELECT 'engineering_datum' FROM engineering_datum "
5218
30.5k
        "WHERE "
5219
30.5k
        "auth_name = ? AND code = ?",
5220
30.5k
        {d->authority(), code, d->authority(), code, d->authority(), code});
5221
30.5k
    if (res.empty()) {
5222
0
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
5223
0
                                           code);
5224
0
    }
5225
30.5k
    const auto &type = res.front()[0];
5226
30.5k
    if (type == "geodetic_datum") {
5227
30.4k
        return createGeodeticDatum(code);
5228
30.4k
    }
5229
94
    if (type == "vertical_datum") {
5230
94
        return createVerticalDatum(code);
5231
94
    }
5232
0
    return createEngineeringDatum(code);
5233
94
}
5234
5235
// ---------------------------------------------------------------------------
5236
5237
//! @cond Doxygen_Suppress
5238
94
static cs::MeridianPtr createMeridian(const std::string &val) {
5239
94
    try {
5240
94
        const std::string degW(std::string("\xC2\xB0") + "W");
5241
94
        if (ends_with(val, degW)) {
5242
19
            return cs::Meridian::create(common::Angle(
5243
19
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
5244
19
        }
5245
75
        const std::string degE(std::string("\xC2\xB0") + "E");
5246
75
        if (ends_with(val, degE)) {
5247
75
            return cs::Meridian::create(common::Angle(
5248
75
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
5249
75
        }
5250
75
    } catch (const std::exception &) {
5251
0
    }
5252
0
    return nullptr;
5253
94
}
5254
//! @endcond
5255
5256
// ---------------------------------------------------------------------------
5257
5258
/** \brief Returns a cs::CoordinateSystem from the specified code.
5259
 *
5260
 * @param code Object code allocated by authority.
5261
 * @return object.
5262
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5263
 * @throw FactoryException in case of other errors.
5264
 */
5265
5266
cs::CoordinateSystemNNPtr
5267
53.3k
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
5268
53.3k
    const auto cacheKey(d->authority() + code);
5269
53.3k
    {
5270
53.3k
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
5271
53.3k
        if (cs) {
5272
35.5k
            return NN_NO_CHECK(cs);
5273
35.5k
        }
5274
53.3k
    }
5275
17.8k
    auto res = d->runWithCodeParam(
5276
17.8k
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
5277
17.8k
        "cs.type FROM "
5278
17.8k
        "axis LEFT JOIN coordinate_system cs ON "
5279
17.8k
        "axis.coordinate_system_auth_name = cs.auth_name AND "
5280
17.8k
        "axis.coordinate_system_code = cs.code WHERE "
5281
17.8k
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
5282
17.8k
        "BY coordinate_system_order",
5283
17.8k
        code);
5284
17.8k
    if (res.empty()) {
5285
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
5286
0
                                           d->authority(), code);
5287
0
    }
5288
5289
17.8k
    const auto &csType = res.front()[5];
5290
17.8k
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
5291
44.2k
    for (const auto &row : res) {
5292
44.2k
        const auto &name = row[0];
5293
44.2k
        const auto &abbrev = row[1];
5294
44.2k
        const auto &orientation = row[2];
5295
44.2k
        const auto &uom_auth_name = row[3];
5296
44.2k
        const auto &uom_code = row[4];
5297
44.2k
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
5298
0
            throw FactoryException("no unit of measure for an axis is only "
5299
0
                                   "supported for ordinatal CS");
5300
0
        }
5301
44.2k
        auto uom = uom_auth_name.empty()
5302
44.2k
                       ? common::UnitOfMeasure::NONE
5303
44.2k
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
5304
44.2k
        auto props =
5305
44.2k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
5306
44.2k
        const cs::AxisDirection *direction =
5307
44.2k
            cs::AxisDirection::valueOf(orientation);
5308
44.2k
        cs::MeridianPtr meridian;
5309
44.2k
        if (direction == nullptr) {
5310
94
            if (orientation == "Geocentre > equator/0"
5311
94
                               "\xC2\xB0"
5312
94
                               "E") {
5313
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
5314
94
            } else if (orientation == "Geocentre > equator/90"
5315
94
                                      "\xC2\xB0"
5316
94
                                      "E") {
5317
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
5318
94
            } else if (orientation == "Geocentre > north pole") {
5319
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
5320
94
            } else if (starts_with(orientation, "North along ")) {
5321
50
                direction = &(cs::AxisDirection::NORTH);
5322
50
                meridian =
5323
50
                    createMeridian(orientation.substr(strlen("North along ")));
5324
50
            } else if (starts_with(orientation, "South along ")) {
5325
44
                direction = &(cs::AxisDirection::SOUTH);
5326
44
                meridian =
5327
44
                    createMeridian(orientation.substr(strlen("South along ")));
5328
44
            } else {
5329
0
                throw FactoryException("unknown axis direction: " +
5330
0
                                       orientation);
5331
0
            }
5332
94
        }
5333
44.2k
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5334
44.2k
            props, abbrev, *direction, uom, meridian));
5335
44.2k
    }
5336
5337
17.8k
    const auto cacheAndRet = [this,
5338
17.8k
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5339
17.8k
        d->context()->d->cache(cacheKey, cs);
5340
17.8k
        return cs;
5341
17.8k
    };
5342
5343
17.8k
    auto props = util::PropertyMap()
5344
17.8k
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5345
17.8k
                     .set(metadata::Identifier::CODE_KEY, code);
5346
17.8k
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5347
12.7k
        if (axisList.size() == 2) {
5348
6.86k
            return cacheAndRet(
5349
6.86k
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5350
6.86k
        }
5351
5.83k
        if (axisList.size() == 3) {
5352
5.83k
            return cacheAndRet(cs::EllipsoidalCS::create(
5353
5.83k
                props, axisList[0], axisList[1], axisList[2]));
5354
5.83k
        }
5355
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5356
5.83k
    }
5357
5.15k
    if (csType == CS_TYPE_CARTESIAN) {
5358
4.55k
        if (axisList.size() == 2) {
5359
1.22k
            return cacheAndRet(
5360
1.22k
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5361
1.22k
        }
5362
3.33k
        if (axisList.size() == 3) {
5363
3.33k
            return cacheAndRet(cs::CartesianCS::create(
5364
3.33k
                props, axisList[0], axisList[1], axisList[2]));
5365
3.33k
        }
5366
0
        throw FactoryException("invalid number of axis for CartesianCS");
5367
3.33k
    }
5368
601
    if (csType == CS_TYPE_SPHERICAL) {
5369
12
        if (axisList.size() == 2) {
5370
12
            return cacheAndRet(
5371
12
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5372
12
        }
5373
0
        if (axisList.size() == 3) {
5374
0
            return cacheAndRet(cs::SphericalCS::create(
5375
0
                props, axisList[0], axisList[1], axisList[2]));
5376
0
        }
5377
0
        throw FactoryException("invalid number of axis for SphericalCS");
5378
0
    }
5379
589
    if (csType == CS_TYPE_VERTICAL) {
5380
589
        if (axisList.size() == 1) {
5381
589
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5382
589
        }
5383
0
        throw FactoryException("invalid number of axis for VerticalCS");
5384
589
    }
5385
0
    if (csType == CS_TYPE_ORDINAL) {
5386
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5387
0
    }
5388
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5389
0
}
5390
5391
// ---------------------------------------------------------------------------
5392
5393
/** \brief Returns a crs::GeodeticCRS from the specified code.
5394
 *
5395
 * @param code Object code allocated by authority.
5396
 * @return object.
5397
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5398
 * @throw FactoryException in case of other errors.
5399
 */
5400
5401
crs::GeodeticCRSNNPtr
5402
193k
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5403
193k
    return createGeodeticCRS(code, false);
5404
193k
}
5405
5406
// ---------------------------------------------------------------------------
5407
5408
/** \brief Returns a crs::GeographicCRS from the specified code.
5409
 *
5410
 * @param code Object code allocated by authority.
5411
 * @return object.
5412
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5413
 * @throw FactoryException in case of other errors.
5414
 */
5415
5416
crs::GeographicCRSNNPtr
5417
1.13k
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5418
1.13k
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5419
1.13k
        createGeodeticCRS(code, true)));
5420
1.13k
    if (!crs) {
5421
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5422
0
                                           d->authority(), code);
5423
0
    }
5424
1.13k
    return NN_NO_CHECK(crs);
5425
1.13k
}
5426
5427
// ---------------------------------------------------------------------------
5428
5429
static crs::GeodeticCRSNNPtr
5430
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5431
0
               const util::PropertyMap &props) {
5432
0
    auto cs = geodCRS->coordinateSystem();
5433
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5434
0
    if (ellipsoidalCS) {
5435
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5436
0
                                          geodCRS->datumEnsemble(),
5437
0
                                          NN_NO_CHECK(ellipsoidalCS));
5438
0
    }
5439
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5440
0
    if (geocentricCS) {
5441
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5442
0
                                        geodCRS->datumEnsemble(),
5443
0
                                        NN_NO_CHECK(geocentricCS));
5444
0
    }
5445
0
    return geodCRS;
5446
0
}
5447
5448
// ---------------------------------------------------------------------------
5449
5450
crs::GeodeticCRSNNPtr
5451
AuthorityFactory::createGeodeticCRS(const std::string &code,
5452
194k
                                    bool geographicOnly) const {
5453
194k
    const auto cacheKey(d->authority() + code);
5454
194k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5455
194k
    if (crs) {
5456
145k
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5457
145k
        if (geogCRS) {
5458
145k
            return NN_NO_CHECK(geogCRS);
5459
145k
        }
5460
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5461
0
                                           d->authority(), code);
5462
145k
    }
5463
49.4k
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5464
49.4k
                    "coordinate_system_code, datum_auth_name, datum_code, "
5465
49.4k
                    "text_definition, deprecated, description FROM "
5466
49.4k
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5467
49.4k
    if (geographicOnly) {
5468
31
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5469
31
               ")";
5470
31
    }
5471
49.4k
    auto res = d->runWithCodeParam(sql, code);
5472
49.4k
    if (res.empty()) {
5473
31
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5474
31
                                           d->authority(), code);
5475
31
    }
5476
49.4k
    try {
5477
49.4k
        const auto &row = res.front();
5478
49.4k
        const auto &name = row[0];
5479
49.4k
        const auto &type = row[1];
5480
49.4k
        const auto &cs_auth_name = row[2];
5481
49.4k
        const auto &cs_code = row[3];
5482
49.4k
        const auto &datum_auth_name = row[4];
5483
49.4k
        const auto &datum_code = row[5];
5484
49.4k
        const auto &text_definition = row[6];
5485
49.4k
        const bool deprecated = row[7] == "1";
5486
49.4k
        const auto &remarks = row[8];
5487
5488
49.4k
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5489
49.4k
                                                     deprecated, remarks);
5490
5491
49.4k
        if (!text_definition.empty()) {
5492
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5493
0
            auto obj = createFromUserInput(
5494
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5495
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5496
0
            if (geodCRS) {
5497
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5498
0
                d->context()->d->cache(cacheKey, crsRet);
5499
0
                return crsRet;
5500
0
            }
5501
5502
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5503
0
            if (boundCRS) {
5504
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5505
0
                    boundCRS->baseCRS());
5506
0
                if (geodCRS) {
5507
0
                    auto newBoundCRS = crs::BoundCRS::create(
5508
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5509
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5510
0
                    return NN_NO_CHECK(
5511
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5512
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5513
0
                }
5514
0
            }
5515
5516
0
            throw FactoryException(
5517
0
                "text_definition does not define a GeodeticCRS");
5518
0
        }
5519
5520
49.4k
        auto cs =
5521
49.4k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5522
49.4k
        datum::GeodeticReferenceFramePtr datum;
5523
49.4k
        datum::DatumEnsemblePtr datumEnsemble;
5524
49.4k
        constexpr bool turnEnsembleAsDatum = false;
5525
49.4k
        d->createFactory(datum_auth_name)
5526
49.4k
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5527
49.4k
                                            turnEnsembleAsDatum);
5528
5529
49.4k
        auto ellipsoidalCS =
5530
49.4k
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5531
49.4k
        if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) {
5532
35.7k
            auto crsRet = crs::GeographicCRS::create(
5533
35.7k
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5534
35.7k
            d->context()->d->cache(cacheKey, crsRet);
5535
35.7k
            return crsRet;
5536
35.7k
        }
5537
5538
13.6k
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5539
13.6k
        if (type == GEOCENTRIC && geocentricCS) {
5540
13.6k
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5541
13.6k
                                                   NN_NO_CHECK(geocentricCS));
5542
13.6k
            d->context()->d->cache(cacheKey, crsRet);
5543
13.6k
            return crsRet;
5544
13.6k
        }
5545
5546
19
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5547
19
        if (type == OTHER && sphericalCS) {
5548
19
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5549
19
                                                   NN_NO_CHECK(sphericalCS));
5550
19
            d->context()->d->cache(cacheKey, crsRet);
5551
19
            return crsRet;
5552
19
        }
5553
5554
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5555
0
                               type + ", " + cs->getWKT2Type(true));
5556
19
    } catch (const std::exception &ex) {
5557
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5558
0
    }
5559
49.4k
}
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.37k
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5573
1.37k
    const auto cacheKey(d->authority() + code);
5574
1.37k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5575
1.37k
    if (crs) {
5576
269
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5577
269
        if (projCRS) {
5578
269
            return NN_NO_CHECK(projCRS);
5579
269
        }
5580
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5581
0
                                           d->authority(), code);
5582
269
    }
5583
1.10k
    auto res = d->runWithCodeParam(
5584
1.10k
        "SELECT name, coordinate_system_auth_name, "
5585
1.10k
        "coordinate_system_code, datum_auth_name, datum_code, "
5586
1.10k
        "deprecated FROM "
5587
1.10k
        "vertical_crs WHERE auth_name = ? AND code = ?",
5588
1.10k
        code);
5589
1.10k
    if (res.empty()) {
5590
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5591
0
                                           d->authority(), code);
5592
0
    }
5593
1.10k
    try {
5594
1.10k
        const auto &row = res.front();
5595
1.10k
        const auto &name = row[0];
5596
1.10k
        const auto &cs_auth_name = row[1];
5597
1.10k
        const auto &cs_code = row[2];
5598
1.10k
        const auto &datum_auth_name = row[3];
5599
1.10k
        const auto &datum_code = row[4];
5600
1.10k
        const bool deprecated = row[5] == "1";
5601
1.10k
        auto cs =
5602
1.10k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5603
1.10k
        datum::VerticalReferenceFramePtr datum;
5604
1.10k
        datum::DatumEnsemblePtr datumEnsemble;
5605
1.10k
        constexpr bool turnEnsembleAsDatum = false;
5606
1.10k
        d->createFactory(datum_auth_name)
5607
1.10k
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5608
1.10k
                                            turnEnsembleAsDatum);
5609
1.10k
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5610
1.10k
                                                     deprecated);
5611
5612
1.10k
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5613
1.10k
        if (verticalCS) {
5614
1.10k
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5615
1.10k
                                                   NN_NO_CHECK(verticalCS));
5616
1.10k
            d->context()->d->cache(cacheKey, crsRet);
5617
1.10k
            return crsRet;
5618
1.10k
        }
5619
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5620
0
                               cs->getWKT2Type(true));
5621
1.10k
    } catch (const std::exception &ex) {
5622
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5623
0
    }
5624
1.10k
}
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
11
AuthorityFactory::createEngineeringCRS(const std::string &code) const {
5639
11
    const auto cacheKey(d->authority() + code);
5640
11
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5641
11
    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
11
    auto res = d->runWithCodeParam(
5650
11
        "SELECT name, coordinate_system_auth_name, "
5651
11
        "coordinate_system_code, datum_auth_name, datum_code, "
5652
11
        "deprecated FROM "
5653
11
        "engineering_crs WHERE auth_name = ? AND code = ?",
5654
11
        code);
5655
11
    if (res.empty()) {
5656
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5657
0
                                           d->authority(), code);
5658
0
    }
5659
11
    try {
5660
11
        const auto &row = res.front();
5661
11
        const auto &name = row[0];
5662
11
        const auto &cs_auth_name = row[1];
5663
11
        const auto &cs_code = row[2];
5664
11
        const auto &datum_auth_name = row[3];
5665
11
        const auto &datum_code = row[4];
5666
11
        const bool deprecated = row[5] == "1";
5667
11
        auto cs =
5668
11
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5669
11
        auto datum = d->createFactory(datum_auth_name)
5670
11
                         ->createEngineeringDatum(datum_code);
5671
11
        auto props = d->createPropertiesSearchUsages("engineering_crs", code,
5672
11
                                                     name, deprecated);
5673
11
        auto crsRet = crs::EngineeringCRS::create(props, datum, cs);
5674
11
        d->context()->d->cache(cacheKey, crsRet);
5675
11
        return crsRet;
5676
11
    } catch (const std::exception &ex) {
5677
0
        throw buildFactoryException("engineeringCRS", d->authority(), code, ex);
5678
0
    }
5679
11
}
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.41k
AuthorityFactory::createConversion(const std::string &code) const {
5693
5694
3.41k
    static const char *sql =
5695
3.41k
        "SELECT name, description, "
5696
3.41k
        "method_auth_name, method_code, method_name, "
5697
5698
3.41k
        "param1_auth_name, param1_code, param1_name, param1_value, "
5699
3.41k
        "param1_uom_auth_name, param1_uom_code, "
5700
5701
3.41k
        "param2_auth_name, param2_code, param2_name, param2_value, "
5702
3.41k
        "param2_uom_auth_name, param2_uom_code, "
5703
5704
3.41k
        "param3_auth_name, param3_code, param3_name, param3_value, "
5705
3.41k
        "param3_uom_auth_name, param3_uom_code, "
5706
5707
3.41k
        "param4_auth_name, param4_code, param4_name, param4_value, "
5708
3.41k
        "param4_uom_auth_name, param4_uom_code, "
5709
5710
3.41k
        "param5_auth_name, param5_code, param5_name, param5_value, "
5711
3.41k
        "param5_uom_auth_name, param5_uom_code, "
5712
5713
3.41k
        "param6_auth_name, param6_code, param6_name, param6_value, "
5714
3.41k
        "param6_uom_auth_name, param6_uom_code, "
5715
5716
3.41k
        "param7_auth_name, param7_code, param7_name, param7_value, "
5717
3.41k
        "param7_uom_auth_name, param7_uom_code, "
5718
5719
3.41k
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5720
5721
3.41k
    auto res = d->runWithCodeParam(sql, code);
5722
3.41k
    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.41k
    try {
5741
3.41k
        const auto &row = res.front();
5742
3.41k
        size_t idx = 0;
5743
3.41k
        const auto &name = row[idx++];
5744
3.41k
        const auto &description = row[idx++];
5745
3.41k
        const auto &method_auth_name = row[idx++];
5746
3.41k
        const auto &method_code = row[idx++];
5747
3.41k
        const auto &method_name = row[idx++];
5748
3.41k
        const size_t base_param_idx = idx;
5749
3.41k
        std::vector<operation::OperationParameterNNPtr> parameters;
5750
3.41k
        std::vector<operation::ParameterValueNNPtr> values;
5751
20.4k
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5752
20.3k
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5753
20.3k
            if (param_auth_name.empty()) {
5754
3.36k
                break;
5755
3.36k
            }
5756
17.0k
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5757
17.0k
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5758
17.0k
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5759
17.0k
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5760
17.0k
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5761
17.0k
            parameters.emplace_back(operation::OperationParameter::create(
5762
17.0k
                util::PropertyMap()
5763
17.0k
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5764
17.0k
                    .set(metadata::Identifier::CODE_KEY, param_code)
5765
17.0k
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5766
17.0k
            std::string normalized_uom_code(param_uom_code);
5767
17.0k
            const double normalized_value = normalizeMeasure(
5768
17.0k
                param_uom_code, param_value, normalized_uom_code);
5769
17.0k
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5770
17.0k
                                              normalized_uom_code);
5771
17.0k
            values.emplace_back(operation::ParameterValue::create(
5772
17.0k
                common::Measure(normalized_value, uom)));
5773
17.0k
        }
5774
3.41k
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5775
5776
3.41k
        auto propConversion = d->createPropertiesSearchUsages(
5777
3.41k
            "conversion", code, name, deprecated);
5778
3.41k
        if (!description.empty())
5779
1.50k
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5780
1.50k
                               description);
5781
5782
3.41k
        auto propMethod = util::PropertyMap().set(
5783
3.41k
            common::IdentifiedObject::NAME_KEY, method_name);
5784
3.41k
        if (!method_auth_name.empty()) {
5785
3.41k
            propMethod
5786
3.41k
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5787
3.41k
                .set(metadata::Identifier::CODE_KEY, method_code);
5788
3.41k
        }
5789
5790
3.41k
        return operation::Conversion::create(propConversion, propMethod,
5791
3.41k
                                             parameters, values);
5792
3.41k
    } catch (const std::exception &ex) {
5793
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5794
0
    }
5795
3.41k
}
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.21k
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5809
3.21k
    const auto cacheKey(d->authority() + code);
5810
3.21k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5811
3.21k
    if (crs) {
5812
53
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5813
53
        if (projCRS) {
5814
53
            return NN_NO_CHECK(projCRS);
5815
53
        }
5816
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5817
0
                                           d->authority(), code);
5818
53
    }
5819
3.16k
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5820
3.21k
}
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.16k
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5832
3.16k
    return runWithCodeParam(
5833
3.16k
        "SELECT name, coordinate_system_auth_name, "
5834
3.16k
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5835
3.16k
        "conversion_auth_name, conversion_code, "
5836
3.16k
        "text_definition, "
5837
3.16k
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5838
3.16k
        code);
5839
3.16k
}
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.16k
                                                 const SQLResultSet &res) {
5847
3.16k
    const auto cacheKey(authority() + code);
5848
3.16k
    if (res.empty()) {
5849
9
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5850
9
                                           authority(), code);
5851
9
    }
5852
3.15k
    try {
5853
3.15k
        const auto &row = res.front();
5854
3.15k
        const auto &name = row[0];
5855
3.15k
        const auto &cs_auth_name = row[1];
5856
3.15k
        const auto &cs_code = row[2];
5857
3.15k
        const auto &geodetic_crs_auth_name = row[3];
5858
3.15k
        const auto &geodetic_crs_code = row[4];
5859
3.15k
        const auto &conversion_auth_name = row[5];
5860
3.15k
        const auto &conversion_code = row[6];
5861
3.15k
        const auto &text_definition = row[7];
5862
3.15k
        const bool deprecated = row[8] == "1";
5863
5864
3.15k
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5865
3.15k
                                                  deprecated);
5866
5867
3.15k
        if (!text_definition.empty()) {
5868
301
            DatabaseContext::Private::RecursionDetector detector(context());
5869
301
            auto obj = createFromUserInput(
5870
301
                pj_add_type_crs_if_needed(text_definition), context());
5871
301
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5872
301
            if (projCRS) {
5873
301
                auto conv = projCRS->derivingConversion();
5874
301
                auto newConv =
5875
301
                    (conv->nameStr() == "unnamed")
5876
301
                        ? operation::Conversion::create(
5877
279
                              util::PropertyMap().set(
5878
279
                                  common::IdentifiedObject::NAME_KEY, name),
5879
279
                              conv->method(), conv->parameterValues())
5880
301
                        : std::move(conv);
5881
301
                auto crsRet = crs::ProjectedCRS::create(
5882
301
                    props, projCRS->baseCRS(), newConv,
5883
301
                    projCRS->coordinateSystem());
5884
301
                context()->d->cache(cacheKey, crsRet);
5885
301
                return crsRet;
5886
301
            }
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
2.85k
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5909
5910
2.85k
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5911
2.85k
                           ->createGeodeticCRS(geodetic_crs_code);
5912
5913
2.85k
        auto conv = createFactory(conversion_auth_name)
5914
2.85k
                        ->createConversion(conversion_code);
5915
2.85k
        if (conv->nameStr() == "unnamed") {
5916
347
            conv = conv->shallowClone();
5917
347
            conv->setProperties(util::PropertyMap().set(
5918
347
                common::IdentifiedObject::NAME_KEY, name));
5919
347
        }
5920
5921
2.85k
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5922
2.85k
        if (cartesianCS) {
5923
2.85k
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5924
2.85k
                                                    NN_NO_CHECK(cartesianCS));
5925
2.85k
            context()->d->cache(cacheKey, crsRet);
5926
2.85k
            return crsRet;
5927
2.85k
        }
5928
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5929
0
                               cs->getWKT2Type(true));
5930
2.85k
    } catch (const std::exception &ex) {
5931
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5932
0
    }
5933
3.15k
}
5934
//! @endcond
5935
5936
// ---------------------------------------------------------------------------
5937
5938
/** \brief Returns a crs::CompoundCRS 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
5946
crs::CompoundCRSNNPtr
5947
207
AuthorityFactory::createCompoundCRS(const std::string &code) const {
5948
207
    auto res =
5949
207
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
5950
207
                            "vertical_crs_auth_name, vertical_crs_code, "
5951
207
                            "deprecated FROM "
5952
207
                            "compound_crs WHERE auth_name = ? AND code = ?",
5953
207
                            code);
5954
207
    if (res.empty()) {
5955
0
        throw NoSuchAuthorityCodeException("compoundCRS not found",
5956
0
                                           d->authority(), code);
5957
0
    }
5958
207
    try {
5959
207
        const auto &row = res.front();
5960
207
        const auto &name = row[0];
5961
207
        const auto &horiz_crs_auth_name = row[1];
5962
207
        const auto &horiz_crs_code = row[2];
5963
207
        const auto &vertical_crs_auth_name = row[3];
5964
207
        const auto &vertical_crs_code = row[4];
5965
207
        const bool deprecated = row[5] == "1";
5966
5967
207
        auto horizCRS =
5968
207
            d->createFactory(horiz_crs_auth_name)
5969
207
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
5970
207
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
5971
207
                           ->createVerticalCRS(vertical_crs_code);
5972
5973
207
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
5974
207
                                                     deprecated);
5975
207
        return crs::CompoundCRS::create(
5976
207
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
5977
207
                                              std::move(vertCRS)});
5978
207
    } catch (const std::exception &ex) {
5979
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
5980
0
    }
5981
207
}
5982
5983
// ---------------------------------------------------------------------------
5984
5985
/** \brief Returns a crs::CRS from the specified code.
5986
 *
5987
 * @param code Object code allocated by authority.
5988
 * @return object.
5989
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5990
 * @throw FactoryException in case of other errors.
5991
 */
5992
5993
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
5994
825k
    const std::string &code) const {
5995
825k
    return createCoordinateReferenceSystem(code, true);
5996
825k
}
5997
5998
//! @cond Doxygen_Suppress
5999
6000
crs::CRSNNPtr
6001
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
6002
825k
                                                  bool allowCompound) const {
6003
825k
    const auto cacheKey(d->authority() + code);
6004
825k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
6005
825k
    if (crs) {
6006
813k
        return NN_NO_CHECK(crs);
6007
813k
    }
6008
6009
11.9k
    if (d->authority() == metadata::Identifier::OGC) {
6010
30
        if (code == "AnsiDate") {
6011
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
6012
0
            return crs::TemporalCRS::create(
6013
0
                util::PropertyMap()
6014
                    // above URL indicates Julian Date" as name... likely wrong
6015
0
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
6016
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6017
0
                    .set(metadata::Identifier::CODE_KEY, code),
6018
0
                datum::TemporalDatum::create(
6019
0
                    util::PropertyMap().set(
6020
0
                        common::IdentifiedObject::NAME_KEY,
6021
0
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
6022
0
                        "as day 1."),
6023
0
                    common::DateTime::create("1600-12-31T00:00:00Z"),
6024
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6025
0
                cs::TemporalCountCS::create(
6026
0
                    util::PropertyMap(),
6027
0
                    cs::CoordinateSystemAxis::create(
6028
0
                        util::PropertyMap().set(
6029
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6030
0
                        "T", cs::AxisDirection::FUTURE,
6031
0
                        common::UnitOfMeasure("day", 0,
6032
0
                                              UnitOfMeasure::Type::TIME))));
6033
0
        }
6034
30
        if (code == "JulianDate") {
6035
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
6036
0
            return crs::TemporalCRS::create(
6037
0
                util::PropertyMap()
6038
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
6039
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6040
0
                    .set(metadata::Identifier::CODE_KEY, code),
6041
0
                datum::TemporalDatum::create(
6042
0
                    util::PropertyMap().set(
6043
0
                        common::IdentifiedObject::NAME_KEY,
6044
0
                        "The beginning of the Julian period."),
6045
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
6046
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6047
0
                cs::TemporalCountCS::create(
6048
0
                    util::PropertyMap(),
6049
0
                    cs::CoordinateSystemAxis::create(
6050
0
                        util::PropertyMap().set(
6051
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6052
0
                        "T", cs::AxisDirection::FUTURE,
6053
0
                        common::UnitOfMeasure("day", 0,
6054
0
                                              UnitOfMeasure::Type::TIME))));
6055
0
        }
6056
30
        if (code == "UnixTime") {
6057
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
6058
0
            return crs::TemporalCRS::create(
6059
0
                util::PropertyMap()
6060
0
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
6061
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6062
0
                    .set(metadata::Identifier::CODE_KEY, code),
6063
0
                datum::TemporalDatum::create(
6064
0
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6065
0
                                            "Unix epoch"),
6066
0
                    common::DateTime::create("1970-01-01T00:00:00Z"),
6067
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6068
0
                cs::TemporalCountCS::create(
6069
0
                    util::PropertyMap(),
6070
0
                    cs::CoordinateSystemAxis::create(
6071
0
                        util::PropertyMap().set(
6072
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6073
0
                        "T", cs::AxisDirection::FUTURE,
6074
0
                        common::UnitOfMeasure::SECOND)));
6075
0
        }
6076
30
        if (code == "84") {
6077
0
            return createCoordinateReferenceSystem("CRS84", false);
6078
0
        }
6079
30
    }
6080
6081
11.9k
    auto res = d->runWithCodeParam(
6082
11.9k
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
6083
11.9k
    if (res.empty()) {
6084
461
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
6085
461
                                           code);
6086
461
    }
6087
11.4k
    const auto &type = res.front()[0];
6088
11.4k
    if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC ||
6089
11.1k
        type == OTHER) {
6090
11.1k
        return createGeodeticCRS(code);
6091
11.1k
    }
6092
355
    if (type == VERTICAL) {
6093
211
        return createVerticalCRS(code);
6094
211
    }
6095
144
    if (type == PROJECTED) {
6096
96
        return createProjectedCRS(code);
6097
96
    }
6098
48
    if (type == ENGINEERING) {
6099
3
        return createEngineeringCRS(code);
6100
3
    }
6101
45
    if (allowCompound && type == COMPOUND) {
6102
45
        return createCompoundCRS(code);
6103
45
    }
6104
0
    throw FactoryException("unhandled CRS type: " + type);
6105
45
}
6106
6107
//! @endcond
6108
6109
// ---------------------------------------------------------------------------
6110
6111
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
6112
 *
6113
 * @param code Object code allocated by authority.
6114
 * @return object.
6115
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6116
 * @throw FactoryException in case of other errors.
6117
 * @since 9.4
6118
 */
6119
6120
coordinates::CoordinateMetadataNNPtr
6121
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
6122
0
    auto res = d->runWithCodeParam(
6123
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
6124
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
6125
0
        code);
6126
0
    if (res.empty()) {
6127
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
6128
0
                                           d->authority(), code);
6129
0
    }
6130
0
    try {
6131
0
        const auto &row = res.front();
6132
0
        const auto &crs_auth_name = row[0];
6133
0
        const auto &crs_code = row[1];
6134
0
        const auto &crs_text_definition = row[2];
6135
0
        const auto &coordinate_epoch = row[3];
6136
6137
0
        auto l_context = d->context();
6138
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
6139
0
        auto crs =
6140
0
            !crs_auth_name.empty()
6141
0
                ? d->createFactory(crs_auth_name)
6142
0
                      ->createCoordinateReferenceSystem(crs_code)
6143
0
                      .as_nullable()
6144
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
6145
0
                      createFromUserInput(crs_text_definition, l_context));
6146
0
        if (!crs) {
6147
0
            throw FactoryException(
6148
0
                std::string("cannot build CoordinateMetadata ") +
6149
0
                d->authority() + ":" + code + ": cannot build CRS");
6150
0
        }
6151
0
        if (coordinate_epoch.empty()) {
6152
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
6153
0
        } else {
6154
0
            return coordinates::CoordinateMetadata::create(
6155
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
6156
0
                l_context.as_nullable());
6157
0
        }
6158
0
    } catch (const std::exception &ex) {
6159
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
6160
0
                                    ex);
6161
0
    }
6162
0
}
6163
6164
// ---------------------------------------------------------------------------
6165
6166
//! @cond Doxygen_Suppress
6167
6168
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
6169
299k
                                               int code) {
6170
299k
    return util::PropertyMap()
6171
299k
        .set(common::IdentifiedObject::NAME_KEY, name)
6172
299k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
6173
299k
        .set(metadata::Identifier::CODE_KEY, code);
6174
299k
}
6175
6176
// ---------------------------------------------------------------------------
6177
6178
299k
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
6179
299k
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
6180
299k
    assert(name);
6181
299k
    return operation::OperationParameter::create(
6182
299k
        createMapNameEPSGCode(name, code));
6183
299k
}
6184
6185
static operation::ParameterValueNNPtr createLength(const std::string &value,
6186
173k
                                                   const UnitOfMeasure &uom) {
6187
173k
    return operation::ParameterValue::create(
6188
173k
        common::Length(c_locale_stod(value), uom));
6189
173k
}
6190
6191
static operation::ParameterValueNNPtr createAngle(const std::string &value,
6192
85.8k
                                                  const UnitOfMeasure &uom) {
6193
85.8k
    return operation::ParameterValue::create(
6194
85.8k
        common::Angle(c_locale_stod(value), uom));
6195
85.8k
}
6196
6197
//! @endcond
6198
6199
// ---------------------------------------------------------------------------
6200
6201
/** \brief Returns a operation::CoordinateOperation from the specified code.
6202
 *
6203
 * @param code Object code allocated by authority.
6204
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6205
 * should be substituted to the official grid names.
6206
 * @return object.
6207
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6208
 * @throw FactoryException in case of other errors.
6209
 */
6210
6211
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6212
1.07k
    const std::string &code, bool usePROJAlternativeGridNames) const {
6213
1.07k
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
6214
1.07k
                                     std::string());
6215
1.07k
}
6216
6217
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6218
    const std::string &code, bool allowConcatenated,
6219
192k
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
6220
192k
    std::string type(typeIn);
6221
192k
    if (type.empty()) {
6222
88.3k
        auto res = d->runWithCodeParam(
6223
88.3k
            "SELECT type FROM coordinate_operation_with_conversion_view "
6224
88.3k
            "WHERE auth_name = ? AND code = ?",
6225
88.3k
            code);
6226
88.3k
        if (res.empty()) {
6227
3
            throw NoSuchAuthorityCodeException("coordinate operation not found",
6228
3
                                               d->authority(), code);
6229
3
        }
6230
88.3k
        type = res.front()[0];
6231
88.3k
    }
6232
6233
192k
    if (type == "conversion") {
6234
74
        return createConversion(code);
6235
74
    }
6236
6237
192k
    if (type == "helmert_transformation") {
6238
6239
46.9k
        auto res = d->runWithCodeParam(
6240
46.9k
            "SELECT name, description, "
6241
46.9k
            "method_auth_name, method_code, method_name, "
6242
46.9k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6243
46.9k
            "target_crs_code, "
6244
46.9k
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
6245
46.9k
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
6246
46.9k
            "rotation_uom_code, scale_difference, "
6247
46.9k
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
6248
46.9k
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
6249
46.9k
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
6250
46.9k
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
6251
46.9k
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
6252
46.9k
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
6253
46.9k
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
6254
46.9k
            "operation_version, deprecated FROM "
6255
46.9k
            "helmert_transformation WHERE auth_name = ? AND code = ?",
6256
46.9k
            code);
6257
46.9k
        if (res.empty()) {
6258
            // shouldn't happen if foreign keys are OK
6259
0
            throw NoSuchAuthorityCodeException(
6260
0
                "helmert_transformation not found", d->authority(), code);
6261
0
        }
6262
46.9k
        try {
6263
46.9k
            const auto &row = res.front();
6264
46.9k
            size_t idx = 0;
6265
46.9k
            const auto &name = row[idx++];
6266
46.9k
            const auto &description = row[idx++];
6267
46.9k
            const auto &method_auth_name = row[idx++];
6268
46.9k
            const auto &method_code = row[idx++];
6269
46.9k
            const auto &method_name = row[idx++];
6270
46.9k
            const auto &source_crs_auth_name = row[idx++];
6271
46.9k
            const auto &source_crs_code = row[idx++];
6272
46.9k
            const auto &target_crs_auth_name = row[idx++];
6273
46.9k
            const auto &target_crs_code = row[idx++];
6274
46.9k
            const auto &accuracy = row[idx++];
6275
6276
46.9k
            const auto &tx = row[idx++];
6277
46.9k
            const auto &ty = row[idx++];
6278
46.9k
            const auto &tz = row[idx++];
6279
46.9k
            const auto &translation_uom_auth_name = row[idx++];
6280
46.9k
            const auto &translation_uom_code = row[idx++];
6281
46.9k
            const auto &rx = row[idx++];
6282
46.9k
            const auto &ry = row[idx++];
6283
46.9k
            const auto &rz = row[idx++];
6284
46.9k
            const auto &rotation_uom_auth_name = row[idx++];
6285
46.9k
            const auto &rotation_uom_code = row[idx++];
6286
46.9k
            const auto &scale_difference = row[idx++];
6287
46.9k
            const auto &scale_difference_uom_auth_name = row[idx++];
6288
46.9k
            const auto &scale_difference_uom_code = row[idx++];
6289
6290
46.9k
            const auto &rate_tx = row[idx++];
6291
46.9k
            const auto &rate_ty = row[idx++];
6292
46.9k
            const auto &rate_tz = row[idx++];
6293
46.9k
            const auto &rate_translation_uom_auth_name = row[idx++];
6294
46.9k
            const auto &rate_translation_uom_code = row[idx++];
6295
46.9k
            const auto &rate_rx = row[idx++];
6296
46.9k
            const auto &rate_ry = row[idx++];
6297
46.9k
            const auto &rate_rz = row[idx++];
6298
46.9k
            const auto &rate_rotation_uom_auth_name = row[idx++];
6299
46.9k
            const auto &rate_rotation_uom_code = row[idx++];
6300
46.9k
            const auto &rate_scale_difference = row[idx++];
6301
46.9k
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
6302
46.9k
            const auto &rate_scale_difference_uom_code = row[idx++];
6303
6304
46.9k
            const auto &epoch = row[idx++];
6305
46.9k
            const auto &epoch_uom_auth_name = row[idx++];
6306
46.9k
            const auto &epoch_uom_code = row[idx++];
6307
6308
46.9k
            const auto &px = row[idx++];
6309
46.9k
            const auto &py = row[idx++];
6310
46.9k
            const auto &pz = row[idx++];
6311
46.9k
            const auto &pivot_uom_auth_name = row[idx++];
6312
46.9k
            const auto &pivot_uom_code = row[idx++];
6313
6314
46.9k
            const auto &operation_version = row[idx++];
6315
46.9k
            const auto &deprecated_str = row[idx++];
6316
46.9k
            const bool deprecated = deprecated_str == "1";
6317
46.9k
            assert(idx == row.size());
6318
6319
46.9k
            auto uom_translation = d->createUnitOfMeasure(
6320
46.9k
                translation_uom_auth_name, translation_uom_code);
6321
6322
46.9k
            auto uom_epoch = epoch_uom_auth_name.empty()
6323
46.9k
                                 ? common::UnitOfMeasure::NONE
6324
46.9k
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
6325
11.2k
                                                          epoch_uom_code);
6326
6327
46.9k
            auto sourceCRS =
6328
46.9k
                d->createFactory(source_crs_auth_name)
6329
46.9k
                    ->createCoordinateReferenceSystem(source_crs_code);
6330
46.9k
            auto targetCRS =
6331
46.9k
                d->createFactory(target_crs_auth_name)
6332
46.9k
                    ->createCoordinateReferenceSystem(target_crs_code);
6333
6334
46.9k
            std::vector<operation::OperationParameterNNPtr> parameters;
6335
46.9k
            std::vector<operation::ParameterValueNNPtr> values;
6336
6337
46.9k
            parameters.emplace_back(createOpParamNameEPSGCode(
6338
46.9k
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
6339
46.9k
            values.emplace_back(createLength(tx, uom_translation));
6340
6341
46.9k
            parameters.emplace_back(createOpParamNameEPSGCode(
6342
46.9k
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
6343
46.9k
            values.emplace_back(createLength(ty, uom_translation));
6344
6345
46.9k
            parameters.emplace_back(createOpParamNameEPSGCode(
6346
46.9k
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
6347
46.9k
            values.emplace_back(createLength(tz, uom_translation));
6348
6349
46.9k
            if (!rx.empty()) {
6350
                // Helmert 7-, 8-, 10- or 15- parameter cases
6351
17.7k
                auto uom_rotation = d->createUnitOfMeasure(
6352
17.7k
                    rotation_uom_auth_name, rotation_uom_code);
6353
6354
17.7k
                parameters.emplace_back(createOpParamNameEPSGCode(
6355
17.7k
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
6356
17.7k
                values.emplace_back(createAngle(rx, uom_rotation));
6357
6358
17.7k
                parameters.emplace_back(createOpParamNameEPSGCode(
6359
17.7k
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
6360
17.7k
                values.emplace_back(createAngle(ry, uom_rotation));
6361
6362
17.7k
                parameters.emplace_back(createOpParamNameEPSGCode(
6363
17.7k
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
6364
17.7k
                values.emplace_back(createAngle(rz, uom_rotation));
6365
6366
17.7k
                auto uom_scale_difference =
6367
17.7k
                    scale_difference_uom_auth_name.empty()
6368
17.7k
                        ? common::UnitOfMeasure::NONE
6369
17.7k
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
6370
17.7k
                                                 scale_difference_uom_code);
6371
6372
17.7k
                parameters.emplace_back(createOpParamNameEPSGCode(
6373
17.7k
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
6374
17.7k
                values.emplace_back(operation::ParameterValue::create(
6375
17.7k
                    common::Scale(c_locale_stod(scale_difference),
6376
17.7k
                                  uom_scale_difference)));
6377
17.7k
            }
6378
6379
46.9k
            if (!rate_tx.empty()) {
6380
                // Helmert 15-parameter
6381
6382
10.8k
                auto uom_rate_translation = d->createUnitOfMeasure(
6383
10.8k
                    rate_translation_uom_auth_name, rate_translation_uom_code);
6384
6385
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6386
10.8k
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
6387
10.8k
                values.emplace_back(
6388
10.8k
                    createLength(rate_tx, uom_rate_translation));
6389
6390
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6391
10.8k
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6392
10.8k
                values.emplace_back(
6393
10.8k
                    createLength(rate_ty, uom_rate_translation));
6394
6395
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6396
10.8k
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6397
10.8k
                values.emplace_back(
6398
10.8k
                    createLength(rate_tz, uom_rate_translation));
6399
6400
10.8k
                auto uom_rate_rotation = d->createUnitOfMeasure(
6401
10.8k
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6402
6403
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6404
10.8k
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6405
10.8k
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6406
6407
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6408
10.8k
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6409
10.8k
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6410
6411
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6412
10.8k
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6413
10.8k
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6414
6415
10.8k
                auto uom_rate_scale_difference =
6416
10.8k
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6417
10.8k
                                           rate_scale_difference_uom_code);
6418
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6419
10.8k
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6420
10.8k
                values.emplace_back(operation::ParameterValue::create(
6421
10.8k
                    common::Scale(c_locale_stod(rate_scale_difference),
6422
10.8k
                                  uom_rate_scale_difference)));
6423
6424
10.8k
                parameters.emplace_back(createOpParamNameEPSGCode(
6425
10.8k
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6426
10.8k
                values.emplace_back(operation::ParameterValue::create(
6427
10.8k
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6428
36.0k
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6429
                // Helmert 8-parameter
6430
399
                parameters.emplace_back(createOpParamNameEPSGCode(
6431
399
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6432
399
                values.emplace_back(operation::ParameterValue::create(
6433
399
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6434
35.6k
            } else if (!px.empty()) {
6435
                // Molodensky-Badekas case
6436
45
                auto uom_pivot =
6437
45
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6438
6439
45
                parameters.emplace_back(createOpParamNameEPSGCode(
6440
45
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6441
45
                values.emplace_back(createLength(px, uom_pivot));
6442
6443
45
                parameters.emplace_back(createOpParamNameEPSGCode(
6444
45
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6445
45
                values.emplace_back(createLength(py, uom_pivot));
6446
6447
45
                parameters.emplace_back(createOpParamNameEPSGCode(
6448
45
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6449
45
                values.emplace_back(createLength(pz, uom_pivot));
6450
45
            }
6451
6452
46.9k
            auto props = d->createPropertiesSearchUsages(
6453
46.9k
                type, code, name, deprecated, description);
6454
46.9k
            if (!operation_version.empty()) {
6455
45.8k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6456
45.8k
                          operation_version);
6457
45.8k
            }
6458
6459
46.9k
            auto propsMethod =
6460
46.9k
                util::PropertyMap()
6461
46.9k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6462
46.9k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6463
46.9k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6464
6465
46.9k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6466
46.9k
            if (!accuracy.empty() && accuracy != "999.0") {
6467
46.4k
                accuracies.emplace_back(
6468
46.4k
                    metadata::PositionalAccuracy::create(accuracy));
6469
46.4k
            }
6470
46.9k
            return operation::Transformation::create(
6471
46.9k
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6472
46.9k
                values, accuracies);
6473
6474
46.9k
        } catch (const std::exception &ex) {
6475
0
            throw buildFactoryException("transformation", d->authority(), code,
6476
0
                                        ex);
6477
0
        }
6478
46.9k
    }
6479
6480
145k
    if (type == "grid_transformation") {
6481
103k
        auto res = d->runWithCodeParam(
6482
103k
            "SELECT name, description, "
6483
103k
            "method_auth_name, method_code, method_name, "
6484
103k
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6485
103k
            "target_crs_code, "
6486
103k
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6487
103k
            "grid_name, "
6488
103k
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6489
103k
            "grid2_name, "
6490
103k
            "param1_auth_name, param1_code, param1_name, param1_value, "
6491
103k
            "param1_uom_auth_name, param1_uom_code, "
6492
103k
            "param2_auth_name, param2_code, param2_name, param2_value, "
6493
103k
            "param2_uom_auth_name, param2_uom_code, "
6494
103k
            "interpolation_crs_auth_name, interpolation_crs_code, "
6495
103k
            "operation_version, deprecated FROM "
6496
103k
            "grid_transformation WHERE auth_name = ? AND code = ?",
6497
103k
            code);
6498
103k
        if (res.empty()) {
6499
            // shouldn't happen if foreign keys are OK
6500
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6501
0
                                               d->authority(), code);
6502
0
        }
6503
103k
        try {
6504
103k
            const auto &row = res.front();
6505
103k
            size_t idx = 0;
6506
103k
            const auto &name = row[idx++];
6507
103k
            const auto &description = row[idx++];
6508
103k
            const auto &method_auth_name = row[idx++];
6509
103k
            const auto &method_code = row[idx++];
6510
103k
            const auto &method_name = row[idx++];
6511
103k
            const auto &source_crs_auth_name = row[idx++];
6512
103k
            const auto &source_crs_code = row[idx++];
6513
103k
            const auto &target_crs_auth_name = row[idx++];
6514
103k
            const auto &target_crs_code = row[idx++];
6515
103k
            const auto &accuracy = row[idx++];
6516
103k
            const auto &grid_param_auth_name = row[idx++];
6517
103k
            const auto &grid_param_code = row[idx++];
6518
103k
            const auto &grid_param_name = row[idx++];
6519
103k
            const auto &grid_name = row[idx++];
6520
103k
            const auto &grid2_param_auth_name = row[idx++];
6521
103k
            const auto &grid2_param_code = row[idx++];
6522
103k
            const auto &grid2_param_name = row[idx++];
6523
103k
            const auto &grid2_name = row[idx++];
6524
103k
            std::vector<operation::OperationParameterNNPtr> parameters;
6525
103k
            std::vector<operation::ParameterValueNNPtr> values;
6526
6527
103k
            parameters.emplace_back(operation::OperationParameter::create(
6528
103k
                util::PropertyMap()
6529
103k
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6530
103k
                    .set(metadata::Identifier::CODESPACE_KEY,
6531
103k
                         grid_param_auth_name)
6532
103k
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6533
103k
            values.emplace_back(
6534
103k
                operation::ParameterValue::createFilename(grid_name));
6535
103k
            if (!grid2_name.empty()) {
6536
90.7k
                parameters.emplace_back(operation::OperationParameter::create(
6537
90.7k
                    util::PropertyMap()
6538
90.7k
                        .set(common::IdentifiedObject::NAME_KEY,
6539
90.7k
                             grid2_param_name)
6540
90.7k
                        .set(metadata::Identifier::CODESPACE_KEY,
6541
90.7k
                             grid2_param_auth_name)
6542
90.7k
                        .set(metadata::Identifier::CODE_KEY,
6543
90.7k
                             grid2_param_code)));
6544
90.7k
                values.emplace_back(
6545
90.7k
                    operation::ParameterValue::createFilename(grid2_name));
6546
90.7k
            }
6547
6548
103k
            const size_t base_param_idx = idx;
6549
103k
            constexpr size_t N_MAX_PARAMS_GRID_TRANSFORMATION = 2;
6550
104k
            for (size_t i = 0; i < N_MAX_PARAMS_GRID_TRANSFORMATION; ++i) {
6551
104k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6552
104k
                if (param_auth_name.empty()) {
6553
103k
                    break;
6554
103k
                }
6555
1.12k
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6556
1.12k
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6557
1.12k
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6558
1.12k
                const auto &param_uom_auth_name =
6559
1.12k
                    row[base_param_idx + i * 6 + 4];
6560
1.12k
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6561
1.12k
                parameters.emplace_back(operation::OperationParameter::create(
6562
1.12k
                    util::PropertyMap()
6563
1.12k
                        .set(metadata::Identifier::CODESPACE_KEY,
6564
1.12k
                             param_auth_name)
6565
1.12k
                        .set(metadata::Identifier::CODE_KEY, param_code)
6566
1.12k
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6567
1.12k
                std::string normalized_uom_code(param_uom_code);
6568
1.12k
                const double normalized_value = normalizeMeasure(
6569
1.12k
                    param_uom_code, param_value, normalized_uom_code);
6570
1.12k
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6571
1.12k
                                                  normalized_uom_code);
6572
1.12k
                values.emplace_back(operation::ParameterValue::create(
6573
1.12k
                    common::Measure(normalized_value, uom)));
6574
1.12k
            }
6575
103k
            idx = base_param_idx + 6 * N_MAX_PARAMS_GRID_TRANSFORMATION;
6576
6577
103k
            const auto &interpolation_crs_auth_name = row[idx++];
6578
103k
            const auto &interpolation_crs_code = row[idx++];
6579
103k
            const auto &operation_version = row[idx++];
6580
103k
            const auto &deprecated_str = row[idx++];
6581
103k
            const bool deprecated = deprecated_str == "1";
6582
103k
            assert(idx == row.size());
6583
6584
103k
            auto sourceCRS =
6585
103k
                d->createFactory(source_crs_auth_name)
6586
103k
                    ->createCoordinateReferenceSystem(source_crs_code);
6587
103k
            auto targetCRS =
6588
103k
                d->createFactory(target_crs_auth_name)
6589
103k
                    ->createCoordinateReferenceSystem(target_crs_code);
6590
103k
            auto interpolationCRS =
6591
103k
                interpolation_crs_auth_name.empty()
6592
103k
                    ? nullptr
6593
103k
                    : d->createFactory(interpolation_crs_auth_name)
6594
1.18k
                          ->createCoordinateReferenceSystem(
6595
1.18k
                              interpolation_crs_code)
6596
1.18k
                          .as_nullable();
6597
6598
103k
            auto props = d->createPropertiesSearchUsages(
6599
103k
                type, code, name, deprecated, description);
6600
103k
            if (!operation_version.empty()) {
6601
103k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6602
103k
                          operation_version);
6603
103k
            }
6604
103k
            auto propsMethod =
6605
103k
                util::PropertyMap()
6606
103k
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6607
103k
                    .set(metadata::Identifier::CODE_KEY, method_code)
6608
103k
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6609
6610
103k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6611
103k
            if (!accuracy.empty() && accuracy != "999.0") {
6612
103k
                accuracies.emplace_back(
6613
103k
                    metadata::PositionalAccuracy::create(accuracy));
6614
103k
            }
6615
6616
            // A bit fragile to detect the operation type with the method name,
6617
            // but not worth changing the database model
6618
103k
            if (starts_with(method_name, "Point motion")) {
6619
83
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6620
0
                    throw operation::InvalidOperation(
6621
0
                        "source_crs and target_crs should be the same for a "
6622
0
                        "PointMotionOperation");
6623
0
                }
6624
6625
83
                auto pmo = operation::PointMotionOperation::create(
6626
83
                    props, sourceCRS, propsMethod, parameters, values,
6627
83
                    accuracies);
6628
83
                if (usePROJAlternativeGridNames) {
6629
83
                    return pmo->substitutePROJAlternativeGridNames(
6630
83
                        d->context());
6631
83
                }
6632
0
                return pmo;
6633
83
            }
6634
6635
103k
            auto transf = operation::Transformation::create(
6636
103k
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6637
103k
                parameters, values, accuracies);
6638
103k
            if (usePROJAlternativeGridNames) {
6639
103k
                return transf->substitutePROJAlternativeGridNames(d->context());
6640
103k
            }
6641
0
            return transf;
6642
6643
103k
        } catch (const std::exception &ex) {
6644
0
            throw buildFactoryException("transformation", d->authority(), code,
6645
0
                                        ex);
6646
0
        }
6647
103k
    }
6648
6649
41.4k
    if (type == "other_transformation") {
6650
4.07k
        std::ostringstream buffer;
6651
4.07k
        buffer.imbue(std::locale::classic());
6652
4.07k
        buffer
6653
4.07k
            << "SELECT name, description, "
6654
4.07k
               "method_auth_name, method_code, method_name, "
6655
4.07k
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6656
4.07k
               "target_crs_code, "
6657
4.07k
               "grid_param_auth_name, grid_param_code, grid_param_name, "
6658
4.07k
               "grid_name, "
6659
4.07k
               "interpolation_crs_auth_name, interpolation_crs_code, "
6660
4.07k
               "operation_version, accuracy, deprecated";
6661
4.07k
        constexpr int N_MAX_PARAMS_OTHER_TRANSFORMATION = 9;
6662
40.7k
        for (size_t i = 1; i <= N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6663
36.7k
            buffer << ", param" << i << "_auth_name";
6664
36.7k
            buffer << ", param" << i << "_code";
6665
36.7k
            buffer << ", param" << i << "_name";
6666
36.7k
            buffer << ", param" << i << "_value";
6667
36.7k
            buffer << ", param" << i << "_uom_auth_name";
6668
36.7k
            buffer << ", param" << i << "_uom_code";
6669
36.7k
        }
6670
4.07k
        buffer << " FROM other_transformation "
6671
4.07k
                  "WHERE auth_name = ? AND code = ?";
6672
6673
4.07k
        auto res = d->runWithCodeParam(buffer.str(), code);
6674
4.07k
        if (res.empty()) {
6675
            // shouldn't happen if foreign keys are OK
6676
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6677
0
                                               d->authority(), code);
6678
0
        }
6679
4.07k
        try {
6680
4.07k
            const auto &row = res.front();
6681
4.07k
            size_t idx = 0;
6682
4.07k
            const auto &name = row[idx++];
6683
4.07k
            const auto &description = row[idx++];
6684
4.07k
            const auto &method_auth_name = row[idx++];
6685
4.07k
            const auto &method_code = row[idx++];
6686
4.07k
            const auto &method_name = row[idx++];
6687
4.07k
            const auto &source_crs_auth_name = row[idx++];
6688
4.07k
            const auto &source_crs_code = row[idx++];
6689
4.07k
            const auto &target_crs_auth_name = row[idx++];
6690
4.07k
            const auto &target_crs_code = row[idx++];
6691
4.07k
            const auto &grid_param_auth_name = row[idx++];
6692
4.07k
            const auto &grid_param_code = row[idx++];
6693
4.07k
            const auto &grid_param_name = row[idx++];
6694
4.07k
            const auto &grid_name = row[idx++];
6695
4.07k
            const auto &interpolation_crs_auth_name = row[idx++];
6696
4.07k
            const auto &interpolation_crs_code = row[idx++];
6697
4.07k
            const auto &operation_version = row[idx++];
6698
4.07k
            const auto &accuracy = row[idx++];
6699
4.07k
            const auto &deprecated_str = row[idx++];
6700
4.07k
            const bool deprecated = deprecated_str == "1";
6701
6702
4.07k
            const size_t base_param_idx = idx;
6703
4.07k
            std::vector<operation::OperationParameterNNPtr> parameters;
6704
4.07k
            std::vector<operation::ParameterValueNNPtr> values;
6705
10.3k
            for (size_t i = 0; i < N_MAX_PARAMS_OTHER_TRANSFORMATION; ++i) {
6706
9.67k
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6707
9.67k
                if (param_auth_name.empty()) {
6708
3.43k
                    break;
6709
3.43k
                }
6710
6.24k
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6711
6.24k
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6712
6.24k
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6713
6.24k
                const auto &param_uom_auth_name =
6714
6.24k
                    row[base_param_idx + i * 6 + 4];
6715
6.24k
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6716
6717
6.24k
                parameters.emplace_back(operation::OperationParameter::create(
6718
6.24k
                    util::PropertyMap()
6719
6.24k
                        .set(metadata::Identifier::CODESPACE_KEY,
6720
6.24k
                             param_auth_name)
6721
6.24k
                        .set(metadata::Identifier::CODE_KEY, param_code)
6722
6.24k
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6723
6.24k
                std::string normalized_uom_code(param_uom_code);
6724
6.24k
                const double normalized_value = normalizeMeasure(
6725
6.24k
                    param_uom_code, param_value, normalized_uom_code);
6726
6.24k
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6727
6.24k
                                                  normalized_uom_code);
6728
6.24k
                values.emplace_back(operation::ParameterValue::create(
6729
6.24k
                    common::Measure(normalized_value, uom)));
6730
6.24k
            }
6731
4.07k
            idx = base_param_idx + 6 * N_MAX_PARAMS_OTHER_TRANSFORMATION;
6732
4.07k
            (void)idx;
6733
4.07k
            assert(idx == row.size());
6734
6735
4.07k
            if (!grid_name.empty()) {
6736
645
                parameters.emplace_back(operation::OperationParameter::create(
6737
645
                    util::PropertyMap()
6738
645
                        .set(common::IdentifiedObject::NAME_KEY,
6739
645
                             grid_param_name)
6740
645
                        .set(metadata::Identifier::CODESPACE_KEY,
6741
645
                             grid_param_auth_name)
6742
645
                        .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6743
645
                values.emplace_back(
6744
645
                    operation::ParameterValue::createFilename(grid_name));
6745
645
            }
6746
6747
4.07k
            auto sourceCRS =
6748
4.07k
                d->createFactory(source_crs_auth_name)
6749
4.07k
                    ->createCoordinateReferenceSystem(source_crs_code);
6750
4.07k
            auto targetCRS =
6751
4.07k
                d->createFactory(target_crs_auth_name)
6752
4.07k
                    ->createCoordinateReferenceSystem(target_crs_code);
6753
4.07k
            auto interpolationCRS =
6754
4.07k
                interpolation_crs_auth_name.empty()
6755
4.07k
                    ? nullptr
6756
4.07k
                    : d->createFactory(interpolation_crs_auth_name)
6757
687
                          ->createCoordinateReferenceSystem(
6758
687
                              interpolation_crs_code)
6759
687
                          .as_nullable();
6760
6761
4.07k
            auto props = d->createPropertiesSearchUsages(
6762
4.07k
                type, code, name, deprecated, description);
6763
4.07k
            if (!operation_version.empty()) {
6764
3.94k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6765
3.94k
                          operation_version);
6766
3.94k
            }
6767
6768
4.07k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6769
4.07k
            if (!accuracy.empty() && accuracy != "999.0") {
6770
4.07k
                accuracies.emplace_back(
6771
4.07k
                    metadata::PositionalAccuracy::create(accuracy));
6772
4.07k
            }
6773
6774
4.07k
            if (method_auth_name == "PROJ") {
6775
3.14k
                if (method_code == "PROJString") {
6776
3.14k
                    auto op = operation::SingleOperation::createPROJBased(
6777
3.14k
                        props, method_name, sourceCRS, targetCRS, accuracies);
6778
3.14k
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6779
3.14k
                    return op;
6780
3.14k
                } else if (method_code == "WKT") {
6781
0
                    auto op = util::nn_dynamic_pointer_cast<
6782
0
                        operation::CoordinateOperation>(
6783
0
                        WKTParser().createFromWKT(method_name));
6784
0
                    if (!op) {
6785
0
                        throw FactoryException("WKT string does not express a "
6786
0
                                               "coordinate operation");
6787
0
                    }
6788
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6789
0
                    return NN_NO_CHECK(op);
6790
0
                }
6791
3.14k
            }
6792
6793
938
            auto propsMethod =
6794
938
                util::PropertyMap()
6795
938
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6796
938
                    .set(metadata::Identifier::CODE_KEY, method_code)
6797
938
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6798
6799
938
            if (method_auth_name == metadata::Identifier::EPSG) {
6800
938
                int method_code_int = std::atoi(method_code.c_str());
6801
938
                if (operation::isAxisOrderReversal(method_code_int) ||
6802
830
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6803
830
                    method_code_int ==
6804
830
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6805
830
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6806
109
                    auto op = operation::Conversion::create(props, propsMethod,
6807
109
                                                            parameters, values);
6808
109
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6809
109
                    return op;
6810
109
                }
6811
938
            }
6812
829
            auto transf = operation::Transformation::create(
6813
829
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6814
829
                parameters, values, accuracies);
6815
829
            if (usePROJAlternativeGridNames) {
6816
829
                return transf->substitutePROJAlternativeGridNames(d->context());
6817
829
            }
6818
0
            return transf;
6819
6820
829
        } catch (const std::exception &ex) {
6821
0
            throw buildFactoryException("transformation", d->authority(), code,
6822
0
                                        ex);
6823
0
        }
6824
4.07k
    }
6825
6826
37.3k
    if (allowConcatenated && type == "concatenated_operation") {
6827
37.3k
        auto res = d->runWithCodeParam(
6828
37.3k
            "SELECT name, description, "
6829
37.3k
            "source_crs_auth_name, source_crs_code, "
6830
37.3k
            "target_crs_auth_name, target_crs_code, "
6831
37.3k
            "accuracy, "
6832
37.3k
            "operation_version, deprecated FROM "
6833
37.3k
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6834
37.3k
            code);
6835
37.3k
        if (res.empty()) {
6836
            // shouldn't happen if foreign keys are OK
6837
0
            throw NoSuchAuthorityCodeException(
6838
0
                "concatenated_operation not found", d->authority(), code);
6839
0
        }
6840
6841
37.3k
        auto resSteps = d->runWithCodeParam(
6842
37.3k
            "SELECT step_auth_name, step_code, step_direction FROM "
6843
37.3k
            "concatenated_operation_step WHERE operation_auth_name = ? "
6844
37.3k
            "AND operation_code = ? ORDER BY step_number",
6845
37.3k
            code);
6846
6847
37.3k
        try {
6848
37.3k
            const auto &row = res.front();
6849
37.3k
            size_t idx = 0;
6850
37.3k
            const auto &name = row[idx++];
6851
37.3k
            const auto &description = row[idx++];
6852
37.3k
            const auto &source_crs_auth_name = row[idx++];
6853
37.3k
            const auto &source_crs_code = row[idx++];
6854
37.3k
            const auto &target_crs_auth_name = row[idx++];
6855
37.3k
            const auto &target_crs_code = row[idx++];
6856
37.3k
            const auto &accuracy = row[idx++];
6857
37.3k
            const auto &operation_version = row[idx++];
6858
37.3k
            const auto &deprecated_str = row[idx++];
6859
37.3k
            const bool deprecated = deprecated_str == "1";
6860
6861
37.3k
            std::vector<operation::CoordinateOperationNNPtr> operations;
6862
37.3k
            size_t countExplicitDirection = 0;
6863
87.3k
            for (const auto &rowStep : resSteps) {
6864
87.3k
                const auto &step_auth_name = rowStep[0];
6865
87.3k
                const auto &step_code = rowStep[1];
6866
87.3k
                const auto &step_direction = rowStep[2];
6867
87.3k
                auto stepOp =
6868
87.3k
                    d->createFactory(step_auth_name)
6869
87.3k
                        ->createCoordinateOperation(step_code, false,
6870
87.3k
                                                    usePROJAlternativeGridNames,
6871
87.3k
                                                    std::string());
6872
87.3k
                if (step_direction == "forward") {
6873
8.11k
                    ++countExplicitDirection;
6874
8.11k
                    operations.push_back(std::move(stepOp));
6875
79.2k
                } else if (step_direction == "reverse") {
6876
1.41k
                    ++countExplicitDirection;
6877
1.41k
                    operations.push_back(stepOp->inverse());
6878
77.7k
                } else {
6879
77.7k
                    operations.push_back(std::move(stepOp));
6880
77.7k
                }
6881
87.3k
            }
6882
6883
37.3k
            if (countExplicitDirection > 0 &&
6884
3.04k
                countExplicitDirection != resSteps.size()) {
6885
0
                throw FactoryException("not all steps have a defined direction "
6886
0
                                       "for concatenated operation " +
6887
0
                                       code);
6888
0
            }
6889
6890
37.3k
            const bool fixDirectionAllowed = (countExplicitDirection == 0);
6891
37.3k
            operation::ConcatenatedOperation::fixSteps(
6892
37.3k
                d->createFactory(source_crs_auth_name)
6893
37.3k
                    ->createCoordinateReferenceSystem(source_crs_code),
6894
37.3k
                d->createFactory(target_crs_auth_name)
6895
37.3k
                    ->createCoordinateReferenceSystem(target_crs_code),
6896
37.3k
                operations, d->context(), fixDirectionAllowed);
6897
6898
37.3k
            auto props = d->createPropertiesSearchUsages(
6899
37.3k
                type, code, name, deprecated, description);
6900
37.3k
            if (!operation_version.empty()) {
6901
35.7k
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6902
35.7k
                          operation_version);
6903
35.7k
            }
6904
6905
37.3k
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6906
37.3k
            if (!accuracy.empty()) {
6907
37.3k
                if (accuracy != "999.0") {
6908
37.3k
                    accuracies.emplace_back(
6909
37.3k
                        metadata::PositionalAccuracy::create(accuracy));
6910
37.3k
                }
6911
37.3k
            } else {
6912
                // Try to compute a reasonable accuracy from the members
6913
13
                double totalAcc = -1;
6914
13
                try {
6915
35
                    for (const auto &op : operations) {
6916
35
                        auto accs = op->coordinateOperationAccuracies();
6917
35
                        if (accs.size() == 1) {
6918
15
                            double acc = c_locale_stod(accs[0]->value());
6919
15
                            if (totalAcc < 0) {
6920
6
                                totalAcc = acc;
6921
9
                            } else {
6922
9
                                totalAcc += acc;
6923
9
                            }
6924
20
                        } else if (dynamic_cast<const operation::Conversion *>(
6925
20
                                       op.get())) {
6926
                            // A conversion is perfectly accurate.
6927
16
                            if (totalAcc < 0) {
6928
7
                                totalAcc = 0;
6929
7
                            }
6930
16
                        } else {
6931
4
                            totalAcc = -1;
6932
4
                            break;
6933
4
                        }
6934
35
                    }
6935
13
                    if (totalAcc >= 0) {
6936
9
                        accuracies.emplace_back(
6937
9
                            metadata::PositionalAccuracy::create(
6938
9
                                toString(totalAcc)));
6939
9
                    }
6940
13
                } catch (const std::exception &) {
6941
0
                }
6942
13
            }
6943
37.3k
            return operation::ConcatenatedOperation::create(props, operations,
6944
37.3k
                                                            accuracies);
6945
6946
37.3k
        } catch (const std::exception &ex) {
6947
0
            throw buildFactoryException("transformation", d->authority(), code,
6948
0
                                        ex);
6949
0
        }
6950
37.3k
    }
6951
6952
0
    throw FactoryException("unhandled coordinate operation type: " + type);
6953
37.3k
}
6954
6955
// ---------------------------------------------------------------------------
6956
6957
/** \brief Returns a list operation::CoordinateOperation between two CRS.
6958
 *
6959
 * The list is ordered with preferred operations first. No attempt is made
6960
 * at inferring operations that are not explicitly in the database.
6961
 *
6962
 * Deprecated operations are rejected.
6963
 *
6964
 * @param sourceCRSCode Source CRS code allocated by authority.
6965
 * @param targetCRSCode Source CRS code allocated by authority.
6966
 * @return list of coordinate operations
6967
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6968
 * @throw FactoryException in case of other errors.
6969
 */
6970
6971
std::vector<operation::CoordinateOperationNNPtr>
6972
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
6973
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
6974
0
    return createFromCoordinateReferenceSystemCodes(
6975
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
6976
0
        false, false, false);
6977
0
}
6978
6979
// ---------------------------------------------------------------------------
6980
6981
/** \brief Returns a list of geoid models available for that crs
6982
 *
6983
 * The list includes the geoid models connected directly with the crs,
6984
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
6985
 *
6986
 * @param code crs code allocated by authority.
6987
 * @return list of geoid model names
6988
 * @throw FactoryException in case of error.
6989
 */
6990
6991
std::list<std::string>
6992
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
6993
6994
0
    ListOfParams params;
6995
0
    std::string sql;
6996
0
    sql += "SELECT DISTINCT GM0.name "
6997
0
           "  FROM geoid_model GM0 "
6998
0
           "INNER JOIN grid_transformation GT0 "
6999
0
           "  ON  GT0.code = GM0.operation_code "
7000
0
           "  AND GT0.auth_name = GM0.operation_auth_name "
7001
0
           "  AND GT0.deprecated = 0 "
7002
0
           "INNER JOIN vertical_crs VC0 "
7003
0
           "  ON VC0.code = GT0.target_crs_code "
7004
0
           "  AND VC0.auth_name = GT0.target_crs_auth_name "
7005
0
           "INNER JOIN vertical_crs VC1 "
7006
0
           "  ON VC1.datum_code = VC0.datum_code "
7007
0
           "  AND VC1.datum_auth_name = VC0.datum_auth_name "
7008
0
           "  AND VC1.code = ? ";
7009
0
    params.emplace_back(code);
7010
0
    if (d->hasAuthorityRestriction()) {
7011
0
        sql += " AND GT0.target_crs_auth_name = ? ";
7012
0
        params.emplace_back(d->authority());
7013
0
    }
7014
0
    sql += " ORDER BY 1 ";
7015
7016
0
    auto sqlRes = d->run(sql, params);
7017
0
    std::list<std::string> res;
7018
0
    for (const auto &row : sqlRes) {
7019
0
        res.push_back(row[0]);
7020
0
    }
7021
0
    return res;
7022
0
}
7023
7024
// ---------------------------------------------------------------------------
7025
7026
/** \brief Returns a list operation::CoordinateOperation between two CRS.
7027
 *
7028
 * The list is ordered with preferred operations first. No attempt is made
7029
 * at inferring operations that are not explicitly in the database (see
7030
 * createFromCRSCodesWithIntermediates() for that), and only
7031
 * source -> target operations are searched (i.e. if target -> source is
7032
 * present, you need to call this method with the arguments reversed, and apply
7033
 * the reverse transformations).
7034
 *
7035
 * Deprecated operations are rejected.
7036
 *
7037
 * If getAuthority() returns empty, then coordinate operations from all
7038
 * authorities are considered.
7039
 *
7040
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7041
 * @param sourceCRSCode Source CRS code allocated by authority
7042
 * sourceCRSAuthName.
7043
 * @param targetCRSAuthName Authority name of targetCRSCode
7044
 * @param targetCRSCode Source CRS code allocated by authority
7045
 * targetCRSAuthName.
7046
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7047
 * should be substituted to the official grid names.
7048
 * @param discardIfMissingGrid Whether coordinate operations that reference
7049
 * missing grids should be removed from the result set.
7050
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7051
 * as available (typically when network is enabled).
7052
 * @param discardSuperseded Whether coordinate operations that are superseded
7053
 * (but not deprecated) should be removed from the result set.
7054
 * @param tryReverseOrder whether to search in the reverse order too (and thus
7055
 * inverse results found that way)
7056
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
7057
 * intersectingExtent2 should be honored in a strict way.
7058
 * @param intersectingExtent1 Optional extent that the resulting operations
7059
 * must intersect.
7060
 * @param intersectingExtent2 Optional extent that the resulting operations
7061
 * must intersect.
7062
 * @return list of coordinate operations
7063
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7064
 * @throw FactoryException in case of other errors.
7065
 */
7066
7067
std::vector<operation::CoordinateOperationNNPtr>
7068
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7069
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7070
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7071
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7072
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7073
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
7074
    const metadata::ExtentPtr &intersectingExtent1,
7075
257k
    const metadata::ExtentPtr &intersectingExtent2) const {
7076
7077
257k
    auto cacheKey(d->authority());
7078
257k
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
7079
257k
    cacheKey += sourceCRSCode;
7080
257k
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
7081
257k
    cacheKey += targetCRSCode;
7082
257k
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
7083
257k
    cacheKey += (discardIfMissingGrid ? '1' : '0');
7084
257k
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
7085
257k
    cacheKey += (discardSuperseded ? '1' : '0');
7086
257k
    cacheKey += (tryReverseOrder ? '1' : '0');
7087
257k
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
7088
514k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7089
514k
        if (extent) {
7090
275k
            const auto &geogExtent = extent->geographicElements();
7091
275k
            if (geogExtent.size() == 1) {
7092
275k
                auto bbox =
7093
275k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7094
275k
                        geogExtent[0].get());
7095
275k
                if (bbox) {
7096
275k
                    cacheKey += toString(bbox->southBoundLatitude());
7097
275k
                    cacheKey += toString(bbox->westBoundLongitude());
7098
275k
                    cacheKey += toString(bbox->northBoundLatitude());
7099
275k
                    cacheKey += toString(bbox->eastBoundLongitude());
7100
275k
                }
7101
275k
            }
7102
275k
        }
7103
514k
    }
7104
7105
257k
    std::vector<operation::CoordinateOperationNNPtr> list;
7106
7107
257k
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
7108
192k
        return list;
7109
192k
    }
7110
7111
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
7112
    // In which case use the conversion of the ProjectedCRS
7113
64.6k
    if (!targetCRSAuthName.empty()) {
7114
64.6k
        auto targetFactory = d->createFactory(targetCRSAuthName);
7115
64.6k
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
7116
64.6k
                                        targetCRSCode);
7117
64.6k
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
7118
64.6k
            cacheKeyProjectedCRS);
7119
64.6k
        crs::ProjectedCRSPtr targetProjCRS;
7120
64.6k
        if (crs) {
7121
64.6k
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
7122
64.6k
        } else {
7123
0
            const auto sqlRes =
7124
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
7125
0
            if (!sqlRes.empty()) {
7126
0
                try {
7127
0
                    targetProjCRS =
7128
0
                        targetFactory->d
7129
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
7130
0
                            .as_nullable();
7131
0
                } catch (const std::exception &) {
7132
0
                }
7133
0
            }
7134
0
        }
7135
64.6k
        if (targetProjCRS) {
7136
3
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
7137
3
            if (sourceCRSAuthName.empty() ||
7138
3
                (!baseIds.empty() &&
7139
3
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
7140
3
                 baseIds.front()->code() == sourceCRSCode)) {
7141
0
                bool ok = true;
7142
0
                auto conv = targetProjCRS->derivingConversion();
7143
0
                if (d->hasAuthorityRestriction()) {
7144
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
7145
0
                         d->authority();
7146
0
                }
7147
0
                if (ok) {
7148
0
                    list.emplace_back(conv);
7149
0
                    d->context()->d->cache(cacheKey, list);
7150
0
                    return list;
7151
0
                }
7152
0
            }
7153
3
        }
7154
64.6k
    }
7155
7156
64.6k
    std::string sql;
7157
64.6k
    if (discardSuperseded) {
7158
64.6k
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
7159
64.6k
              "cov.target_crs_auth_name, cov.target_crs_code, "
7160
64.6k
              "cov.auth_name, cov.code, cov.table_name, "
7161
64.6k
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7162
64.6k
              "extent.east_lon, "
7163
64.6k
              "ss.replacement_auth_name, ss.replacement_code, "
7164
64.6k
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
7165
64.6k
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
7166
64.6k
              "FROM "
7167
64.6k
              "coordinate_operation_view cov "
7168
64.6k
              "JOIN usage ON "
7169
64.6k
              "usage.object_table_name = cov.table_name AND "
7170
64.6k
              "usage.object_auth_name = cov.auth_name AND "
7171
64.6k
              "usage.object_code = cov.code "
7172
64.6k
              "JOIN extent "
7173
64.6k
              "ON extent.auth_name = usage.extent_auth_name AND "
7174
64.6k
              "extent.code = usage.extent_code "
7175
64.6k
              "LEFT JOIN supersession ss ON "
7176
64.6k
              "ss.superseded_table_name = cov.table_name AND "
7177
64.6k
              "ss.superseded_auth_name = cov.auth_name AND "
7178
64.6k
              "ss.superseded_code = cov.code AND "
7179
64.6k
              "ss.superseded_table_name = ss.replacement_table_name AND "
7180
64.6k
              "ss.same_source_target_crs = 1 "
7181
64.6k
              "LEFT JOIN grid_transformation gt ON "
7182
64.6k
              "gt.auth_name = ss.replacement_auth_name AND "
7183
64.6k
              "gt.code = ss.replacement_code "
7184
64.6k
              "LEFT JOIN grid_alternatives ga ON "
7185
64.6k
              "ga.original_grid_name = gt.grid_name "
7186
64.6k
              "WHERE ";
7187
64.6k
    } else {
7188
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
7189
0
              "target_crs_auth_name, target_crs_code, "
7190
0
              "cov.auth_name, cov.code, cov.table_name, "
7191
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7192
0
              "extent.east_lon "
7193
0
              "FROM "
7194
0
              "coordinate_operation_view cov "
7195
0
              "JOIN usage ON "
7196
0
              "usage.object_table_name = cov.table_name AND "
7197
0
              "usage.object_auth_name = cov.auth_name AND "
7198
0
              "usage.object_code = cov.code "
7199
0
              "JOIN extent "
7200
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7201
0
              "extent.code = usage.extent_code "
7202
0
              "WHERE ";
7203
0
    }
7204
64.6k
    ListOfParams params;
7205
64.6k
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
7206
64.4k
        if (tryReverseOrder) {
7207
64.4k
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7208
64.4k
                   "AND "
7209
64.4k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
7210
64.4k
                   "OR "
7211
64.4k
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7212
64.4k
                   "AND "
7213
64.4k
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
7214
64.4k
                   "AND ";
7215
64.4k
            params.emplace_back(sourceCRSAuthName);
7216
64.4k
            params.emplace_back(sourceCRSCode);
7217
64.4k
            params.emplace_back(targetCRSAuthName);
7218
64.4k
            params.emplace_back(targetCRSCode);
7219
64.4k
            params.emplace_back(targetCRSAuthName);
7220
64.4k
            params.emplace_back(targetCRSCode);
7221
64.4k
            params.emplace_back(sourceCRSAuthName);
7222
64.4k
            params.emplace_back(sourceCRSCode);
7223
64.4k
        } else {
7224
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7225
0
                   "AND "
7226
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7227
0
                   "AND ";
7228
0
            params.emplace_back(sourceCRSAuthName);
7229
0
            params.emplace_back(sourceCRSCode);
7230
0
            params.emplace_back(targetCRSAuthName);
7231
0
            params.emplace_back(targetCRSCode);
7232
0
        }
7233
64.4k
    } else if (!sourceCRSAuthName.empty()) {
7234
0
        if (tryReverseOrder) {
7235
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7236
0
                   ")OR "
7237
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7238
0
                   " AND ";
7239
0
            params.emplace_back(sourceCRSAuthName);
7240
0
            params.emplace_back(sourceCRSCode);
7241
0
            params.emplace_back(sourceCRSAuthName);
7242
0
            params.emplace_back(sourceCRSCode);
7243
0
        } else {
7244
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7245
0
                   "AND ";
7246
0
            params.emplace_back(sourceCRSAuthName);
7247
0
            params.emplace_back(sourceCRSCode);
7248
0
        }
7249
174
    } else if (!targetCRSAuthName.empty()) {
7250
174
        if (tryReverseOrder) {
7251
174
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
7252
174
                   " OR "
7253
174
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7254
174
                   " AND ";
7255
174
            params.emplace_back(targetCRSAuthName);
7256
174
            params.emplace_back(targetCRSCode);
7257
174
            params.emplace_back(targetCRSAuthName);
7258
174
            params.emplace_back(targetCRSCode);
7259
174
        } else {
7260
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7261
0
                   "AND ";
7262
0
            params.emplace_back(targetCRSAuthName);
7263
0
            params.emplace_back(targetCRSCode);
7264
0
        }
7265
174
    }
7266
64.6k
    sql += "cov.deprecated = 0";
7267
64.6k
    if (d->hasAuthorityRestriction()) {
7268
58.8k
        sql += " AND cov.auth_name = ?";
7269
58.8k
        params.emplace_back(d->authority());
7270
58.8k
    }
7271
64.6k
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
7272
64.6k
           "east_lon) DESC, "
7273
64.6k
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
7274
64.6k
    auto res = d->run(sql, params);
7275
64.6k
    std::set<std::pair<std::string, std::string>> setTransf;
7276
64.6k
    if (discardSuperseded) {
7277
64.6k
        for (const auto &row : res) {
7278
46.2k
            const auto &auth_name = row[4];
7279
46.2k
            const auto &code = row[5];
7280
46.2k
            setTransf.insert(
7281
46.2k
                std::pair<std::string, std::string>(auth_name, code));
7282
46.2k
        }
7283
64.6k
    }
7284
7285
    // Do a pass to determine if there are transformations that intersect
7286
    // intersectingExtent1 & intersectingExtent2
7287
64.6k
    std::vector<bool> intersectingTransformations;
7288
64.6k
    intersectingTransformations.resize(res.size());
7289
64.6k
    bool hasIntersectingTransformations = false;
7290
64.6k
    size_t i = 0;
7291
64.6k
    for (const auto &row : res) {
7292
46.2k
        size_t thisI = i;
7293
46.2k
        ++i;
7294
46.2k
        if (discardSuperseded) {
7295
46.2k
            const auto &replacement_auth_name = row[11];
7296
46.2k
            const auto &replacement_code = row[12];
7297
46.2k
            const bool replacement_is_grid_transform = row[13] == "1";
7298
46.2k
            const bool replacement_is_known_grid = row[14] == "1";
7299
46.2k
            if (!replacement_auth_name.empty() &&
7300
                // Ignore supersession if the replacement uses a unknown grid
7301
297
                !(replacement_is_grid_transform &&
7302
248
                  !replacement_is_known_grid) &&
7303
289
                setTransf.find(std::pair<std::string, std::string>(
7304
289
                    replacement_auth_name, replacement_code)) !=
7305
289
                    setTransf.end()) {
7306
                // Skip transformations that are superseded by others that got
7307
                // returned in the result set.
7308
289
                continue;
7309
289
            }
7310
46.2k
        }
7311
7312
45.9k
        bool intersecting = true;
7313
45.9k
        try {
7314
45.9k
            double south_lat = c_locale_stod(row[7]);
7315
45.9k
            double west_lon = c_locale_stod(row[8]);
7316
45.9k
            double north_lat = c_locale_stod(row[9]);
7317
45.9k
            double east_lon = c_locale_stod(row[10]);
7318
45.9k
            auto transf_extent = metadata::Extent::createFromBBOX(
7319
45.9k
                west_lon, south_lat, east_lon, north_lat);
7320
7321
45.9k
            for (const auto &extent :
7322
91.7k
                 {intersectingExtent1, intersectingExtent2}) {
7323
91.7k
                if (extent) {
7324
2.69k
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
7325
318
                        intersecting = false;
7326
318
                        break;
7327
318
                    }
7328
2.69k
                }
7329
91.7k
            }
7330
45.9k
        } catch (const std::exception &) {
7331
0
        }
7332
7333
45.9k
        intersectingTransformations[thisI] = intersecting;
7334
45.9k
        if (intersecting)
7335
45.6k
            hasIntersectingTransformations = true;
7336
45.9k
    }
7337
7338
    // If there are intersecting transformations, then only report those ones
7339
    // If there are no intersecting transformations, report all of them
7340
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
7341
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
7342
    // coordinates are used
7343
64.6k
    i = 0;
7344
64.6k
    for (const auto &row : res) {
7345
46.2k
        size_t thisI = i;
7346
46.2k
        ++i;
7347
46.2k
        if ((hasIntersectingTransformations ||
7348
302
             reportOnlyIntersectingTransformations) &&
7349
46.1k
            !intersectingTransformations[thisI]) {
7350
443
            continue;
7351
443
        }
7352
45.8k
        if (discardSuperseded) {
7353
45.8k
            const auto &replacement_auth_name = row[11];
7354
45.8k
            const auto &replacement_code = row[12];
7355
45.8k
            const bool replacement_is_grid_transform = row[13] == "1";
7356
45.8k
            const bool replacement_is_known_grid = row[14] == "1";
7357
45.8k
            if (!replacement_auth_name.empty() &&
7358
                // Ignore supersession if the replacement uses a unknown grid
7359
7
                !(replacement_is_grid_transform &&
7360
7
                  !replacement_is_known_grid) &&
7361
0
                setTransf.find(std::pair<std::string, std::string>(
7362
0
                    replacement_auth_name, replacement_code)) !=
7363
0
                    setTransf.end()) {
7364
                // Skip transformations that are superseded by others that got
7365
                // returned in the result set.
7366
0
                continue;
7367
0
            }
7368
45.8k
        }
7369
7370
45.8k
        const auto &source_crs_auth_name = row[0];
7371
45.8k
        const auto &source_crs_code = row[1];
7372
45.8k
        const auto &target_crs_auth_name = row[2];
7373
45.8k
        const auto &target_crs_code = row[3];
7374
45.8k
        const auto &auth_name = row[4];
7375
45.8k
        const auto &code = row[5];
7376
45.8k
        const auto &table_name = row[6];
7377
45.8k
        try {
7378
45.8k
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
7379
45.8k
                code, true, usePROJAlternativeGridNames, table_name);
7380
45.8k
            if (tryReverseOrder &&
7381
45.8k
                (!sourceCRSAuthName.empty()
7382
45.8k
                     ? (source_crs_auth_name != sourceCRSAuthName ||
7383
45.6k
                        source_crs_code != sourceCRSCode)
7384
45.8k
                     : (target_crs_auth_name != targetCRSAuthName ||
7385
28.1k
                        target_crs_code != targetCRSCode))) {
7386
28.1k
                op = op->inverse();
7387
28.1k
            }
7388
45.8k
            if (!discardIfMissingGrid ||
7389
45.8k
                !d->rejectOpDueToMissingGrid(op,
7390
45.8k
                                             considerKnownGridsAsAvailable)) {
7391
14.9k
                list.emplace_back(op);
7392
14.9k
            }
7393
45.8k
        } catch (const std::exception &e) {
7394
            // Mostly for debugging purposes when using an inconsistent
7395
            // database
7396
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7397
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7398
0
            } else {
7399
0
                throw;
7400
0
            }
7401
0
        }
7402
45.8k
    }
7403
64.6k
    d->context()->d->cache(cacheKey, list);
7404
64.6k
    return list;
7405
64.6k
}
7406
7407
// ---------------------------------------------------------------------------
7408
7409
//! @cond Doxygen_Suppress
7410
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
7411
                               const std::string &sourceCRSAuthName,
7412
                               const std::string &sourceCRSCode,
7413
                               const std::string &targetCRSAuthName,
7414
58.0k
                               const std::string &targetCRSCode) {
7415
58.0k
    auto concat =
7416
58.0k
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
7417
58.0k
    if (!concat) {
7418
37.9k
        return false;
7419
37.9k
    }
7420
20.0k
    auto ops = concat->operations();
7421
45.5k
    for (size_t i = 0; i + 1 < ops.size(); i++) {
7422
36.6k
        auto targetCRS = ops[i]->targetCRS();
7423
36.6k
        if (targetCRS) {
7424
36.6k
            const auto &ids = targetCRS->identifiers();
7425
36.6k
            if (ids.size() == 1 &&
7426
36.6k
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
7427
34.8k
                  ids[0]->code() == sourceCRSCode) ||
7428
30.6k
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7429
29.2k
                  ids[0]->code() == targetCRSCode))) {
7430
11.1k
                return true;
7431
11.1k
            }
7432
36.6k
        }
7433
36.6k
    }
7434
8.91k
    return false;
7435
20.0k
}
7436
//! @endcond
7437
7438
// ---------------------------------------------------------------------------
7439
7440
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7441
 * using intermediate codes.
7442
 *
7443
 * The list is ordered with preferred operations first.
7444
 *
7445
 * Deprecated operations are rejected.
7446
 *
7447
 * The method will take care of considering all potential combinations (i.e.
7448
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7449
 * call it with sourceCRS and targetCRS switched)
7450
 *
7451
 * If getAuthority() returns empty, then coordinate operations from all
7452
 * authorities are considered.
7453
 *
7454
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7455
 * @param sourceCRSCode Source CRS code allocated by authority
7456
 * sourceCRSAuthName.
7457
 * @param targetCRSAuthName Authority name of targetCRSCode
7458
 * @param targetCRSCode Source CRS code allocated by authority
7459
 * targetCRSAuthName.
7460
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7461
 * should be substituted to the official grid names.
7462
 * @param discardIfMissingGrid Whether coordinate operations that reference
7463
 * missing grids should be removed from the result set.
7464
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7465
 * as available (typically when network is enabled).
7466
 * @param discardSuperseded Whether coordinate operations that are superseded
7467
 * (but not deprecated) should be removed from the result set.
7468
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7469
 * used as potential intermediate CRS. If the list is empty, the database will
7470
 * be used to find common CRS in operations involving both the source and
7471
 * target CRS.
7472
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7473
 * object considered.
7474
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7475
 * @param allowedAuthorities One or several authority name allowed for the two
7476
 * coordinate operations that are going to be searched. When this vector is
7477
 * no empty, it overrides the authority of this object. This is useful for
7478
 * example when the coordinate operations to chain belong to two different
7479
 * allowed authorities.
7480
 * @param intersectingExtent1 Optional extent that the resulting operations
7481
 * must intersect.
7482
 * @param intersectingExtent2 Optional extent that the resulting operations
7483
 * must intersect.
7484
 * @return list of coordinate operations
7485
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7486
 * @throw FactoryException in case of other errors.
7487
 */
7488
7489
std::vector<operation::CoordinateOperationNNPtr>
7490
AuthorityFactory::createFromCRSCodesWithIntermediates(
7491
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7492
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7493
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7494
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7495
    const std::vector<std::pair<std::string, std::string>>
7496
        &intermediateCRSAuthCodes,
7497
    ObjectType allowedIntermediateObjectType,
7498
    const std::vector<std::string> &allowedAuthorities,
7499
    const metadata::ExtentPtr &intersectingExtent1,
7500
52.7k
    const metadata::ExtentPtr &intersectingExtent2) const {
7501
7502
52.7k
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7503
7504
52.7k
    if (sourceCRSAuthName == targetCRSAuthName &&
7505
16.1k
        sourceCRSCode == targetCRSCode) {
7506
111
        return listTmp;
7507
111
    }
7508
7509
52.6k
    const auto CheckIfHasOperations = [this](const std::string &auth_name,
7510
98.1k
                                             const std::string &code) {
7511
98.1k
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7512
98.1k
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7513
98.1k
                        "(target_crs_auth_name = ? AND target_crs_code = ?) "
7514
98.1k
                        "LIMIT 1",
7515
98.1k
                        {auth_name, code, auth_name, code})
7516
98.1k
                     .empty());
7517
98.1k
    };
7518
7519
    // If the source or target CRS are not the source or target of an operation,
7520
    // do not run the next costly requests.
7521
52.6k
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7522
45.4k
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7523
12.7k
        return listTmp;
7524
12.7k
    }
7525
7526
39.8k
    const std::string sqlProlog(
7527
39.8k
        discardSuperseded
7528
39.8k
            ?
7529
7530
39.8k
            "SELECT v1.table_name as table1, "
7531
39.8k
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7532
39.8k
            "v1.accuracy AS accuracy1, "
7533
39.8k
            "v2.table_name as table2, "
7534
39.8k
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7535
39.8k
            "v2.accuracy as accuracy2, "
7536
39.8k
            "a1.south_lat AS south_lat1, "
7537
39.8k
            "a1.west_lon AS west_lon1, "
7538
39.8k
            "a1.north_lat AS north_lat1, "
7539
39.8k
            "a1.east_lon AS east_lon1, "
7540
39.8k
            "a2.south_lat AS south_lat2, "
7541
39.8k
            "a2.west_lon AS west_lon2, "
7542
39.8k
            "a2.north_lat AS north_lat2, "
7543
39.8k
            "a2.east_lon AS east_lon2, "
7544
39.8k
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7545
39.8k
            "ss1.replacement_code AS replacement_code1, "
7546
39.8k
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7547
39.8k
            "ss2.replacement_code AS replacement_code2 "
7548
39.8k
            "FROM coordinate_operation_view v1 "
7549
39.8k
            "JOIN coordinate_operation_view v2 "
7550
39.8k
            :
7551
7552
39.8k
            "SELECT v1.table_name as table1, "
7553
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7554
0
            "v1.accuracy AS accuracy1, "
7555
0
            "v2.table_name as table2, "
7556
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7557
0
            "v2.accuracy as accuracy2, "
7558
0
            "a1.south_lat AS south_lat1, "
7559
0
            "a1.west_lon AS west_lon1, "
7560
0
            "a1.north_lat AS north_lat1, "
7561
0
            "a1.east_lon AS east_lon1, "
7562
0
            "a2.south_lat AS south_lat2, "
7563
0
            "a2.west_lon AS west_lon2, "
7564
0
            "a2.north_lat AS north_lat2, "
7565
0
            "a2.east_lon AS east_lon2 "
7566
0
            "FROM coordinate_operation_view v1 "
7567
0
            "JOIN coordinate_operation_view v2 ");
7568
7569
39.8k
    const char *joinSupersession =
7570
39.8k
        "LEFT JOIN supersession ss1 ON "
7571
39.8k
        "ss1.superseded_table_name = v1.table_name AND "
7572
39.8k
        "ss1.superseded_auth_name = v1.auth_name AND "
7573
39.8k
        "ss1.superseded_code = v1.code AND "
7574
39.8k
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7575
39.8k
        "ss1.same_source_target_crs = 1 "
7576
39.8k
        "LEFT JOIN supersession ss2 ON "
7577
39.8k
        "ss2.superseded_table_name = v2.table_name AND "
7578
39.8k
        "ss2.superseded_auth_name = v2.auth_name AND "
7579
39.8k
        "ss2.superseded_code = v2.code AND "
7580
39.8k
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7581
39.8k
        "ss2.same_source_target_crs = 1 ";
7582
39.8k
    const std::string joinArea(
7583
39.8k
        (discardSuperseded ? std::string(joinSupersession) : std::string())
7584
39.8k
            .append("JOIN usage u1 ON "
7585
39.8k
                    "u1.object_table_name = v1.table_name AND "
7586
39.8k
                    "u1.object_auth_name = v1.auth_name AND "
7587
39.8k
                    "u1.object_code = v1.code "
7588
39.8k
                    "JOIN extent a1 "
7589
39.8k
                    "ON a1.auth_name = u1.extent_auth_name AND "
7590
39.8k
                    "a1.code = u1.extent_code "
7591
39.8k
                    "JOIN usage u2 ON "
7592
39.8k
                    "u2.object_table_name = v2.table_name AND "
7593
39.8k
                    "u2.object_auth_name = v2.auth_name AND "
7594
39.8k
                    "u2.object_code = v2.code "
7595
39.8k
                    "JOIN extent a2 "
7596
39.8k
                    "ON a2.auth_name = u2.extent_auth_name AND "
7597
39.8k
                    "a2.code = u2.extent_code "));
7598
39.8k
    const std::string orderBy(
7599
39.8k
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7600
39.8k
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7601
39.8k
        "accuracy1 + accuracy2");
7602
7603
    // Case (source->intermediate) and (intermediate->target)
7604
39.8k
    std::string sql(
7605
39.8k
        sqlProlog +
7606
39.8k
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7607
39.8k
        "AND v1.target_crs_code = v2.source_crs_code " +
7608
39.8k
        joinArea +
7609
39.8k
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7610
39.8k
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7611
39.8k
    std::string minDate;
7612
39.8k
    std::string criterionOnIntermediateCRS;
7613
7614
39.8k
    const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7615
39.8k
                               ->createCoordinateReferenceSystem(sourceCRSCode);
7616
39.8k
    const auto targetCRS = d->createFactory(targetCRSAuthName)
7617
39.8k
                               ->createCoordinateReferenceSystem(targetCRSCode);
7618
7619
39.8k
    const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7620
5.01k
                            starts_with(targetCRS->nameStr(), "ETRF");
7621
7622
39.8k
    const bool NAD83_CSRS_to_NAD83_CSRS =
7623
39.8k
        starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") &&
7624
3.60k
        starts_with(targetCRS->nameStr(), "NAD83(CSRS)");
7625
7626
39.8k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7627
27.9k
        const auto &sourceGeogCRS =
7628
27.9k
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7629
27.9k
        const auto &targetGeogCRS =
7630
27.9k
            dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7631
27.9k
        if (sourceGeogCRS && targetGeogCRS) {
7632
27.9k
            const auto &sourceDatum = sourceGeogCRS->datum();
7633
27.9k
            const auto &targetDatum = targetGeogCRS->datum();
7634
27.9k
            if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7635
9.53k
                targetDatum && targetDatum->publicationDate().has_value()) {
7636
1.62k
                const auto sourceDate(
7637
1.62k
                    sourceDatum->publicationDate()->toString());
7638
1.62k
                const auto targetDate(
7639
1.62k
                    targetDatum->publicationDate()->toString());
7640
1.62k
                minDate = std::min(sourceDate, targetDate);
7641
                // Check that the datum of the intermediateCRS has a publication
7642
                // date most recent that the one of the source and the target
7643
                // CRS Except when using the usual WGS84 pivot which happens to
7644
                // have a NULL publication date.
7645
1.62k
                criterionOnIntermediateCRS =
7646
1.62k
                    "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7647
1.62k
                    "JOIN geodetic_datum y "
7648
1.62k
                    "ON "
7649
1.62k
                    "y.auth_name = x.datum_auth_name AND "
7650
1.62k
                    "y.code = x.datum_code "
7651
1.62k
                    "WHERE "
7652
1.62k
                    "x.auth_name = v1.target_crs_auth_name AND "
7653
1.62k
                    "x.code = v1.target_crs_code AND "
7654
1.62k
                    "x.type IN ('geographic 2D', 'geographic 3D') AND "
7655
1.62k
                    "(y.publication_date IS NULL OR "
7656
1.62k
                    "(y.publication_date >= '" +
7657
1.62k
                    minDate + "'))) ";
7658
1.62k
            }
7659
27.9k
        }
7660
27.9k
        if (criterionOnIntermediateCRS.empty()) {
7661
26.3k
            criterionOnIntermediateCRS =
7662
26.3k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7663
26.3k
                "x.auth_name = v1.target_crs_auth_name AND "
7664
26.3k
                "x.code = v1.target_crs_code AND "
7665
26.3k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7666
26.3k
        }
7667
27.9k
        sql += criterionOnIntermediateCRS;
7668
27.9k
    }
7669
39.8k
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7670
39.8k
                               targetCRSAuthName, targetCRSCode};
7671
39.8k
    std::string additionalWhere(
7672
39.8k
        "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7673
39.8k
        "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, "
7674
39.8k
        "south_lat2, west_lon2, north_lat2, east_lon2) = 1 ");
7675
39.8k
    if (!allowedAuthorities.empty()) {
7676
31.0k
        additionalWhere += "AND v1.auth_name IN (";
7677
124k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7678
93.2k
            if (i > 0)
7679
62.1k
                additionalWhere += ',';
7680
93.2k
            additionalWhere += '?';
7681
93.2k
        }
7682
31.0k
        additionalWhere += ") AND v2.auth_name IN (";
7683
124k
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7684
93.2k
            if (i > 0)
7685
62.1k
                additionalWhere += ',';
7686
93.2k
            additionalWhere += '?';
7687
93.2k
        }
7688
31.0k
        additionalWhere += ')';
7689
93.2k
        for (const auto &allowedAuthority : allowedAuthorities) {
7690
93.2k
            params.emplace_back(allowedAuthority);
7691
93.2k
        }
7692
93.2k
        for (const auto &allowedAuthority : allowedAuthorities) {
7693
93.2k
            params.emplace_back(allowedAuthority);
7694
93.2k
        }
7695
31.0k
    }
7696
39.8k
    if (d->hasAuthorityRestriction()) {
7697
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7698
0
        params.emplace_back(d->authority());
7699
0
        params.emplace_back(d->authority());
7700
0
    }
7701
79.7k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7702
79.7k
        if (extent) {
7703
41.4k
            const auto &geogExtent = extent->geographicElements();
7704
41.4k
            if (geogExtent.size() == 1) {
7705
41.4k
                auto bbox =
7706
41.4k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7707
41.4k
                        geogExtent[0].get());
7708
41.4k
                if (bbox) {
7709
41.4k
                    const double south_lat = bbox->southBoundLatitude();
7710
41.4k
                    const double west_lon = bbox->westBoundLongitude();
7711
41.4k
                    const double north_lat = bbox->northBoundLatitude();
7712
41.4k
                    const double east_lon = bbox->eastBoundLongitude();
7713
41.4k
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7714
29.5k
                        north_lat != 90.0 || east_lon != 180.0) {
7715
29.5k
                        additionalWhere +=
7716
29.5k
                            "AND intersects_bbox(south_lat1, "
7717
29.5k
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7718
29.5k
                            "intersects_bbox(south_lat2, west_lon2, "
7719
29.5k
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7720
29.5k
                        params.emplace_back(south_lat);
7721
29.5k
                        params.emplace_back(west_lon);
7722
29.5k
                        params.emplace_back(north_lat);
7723
29.5k
                        params.emplace_back(east_lon);
7724
29.5k
                        params.emplace_back(south_lat);
7725
29.5k
                        params.emplace_back(west_lon);
7726
29.5k
                        params.emplace_back(north_lat);
7727
29.5k
                        params.emplace_back(east_lon);
7728
29.5k
                    }
7729
41.4k
                }
7730
41.4k
            }
7731
41.4k
        }
7732
79.7k
    }
7733
7734
39.8k
    const auto buildIntermediateWhere =
7735
39.8k
        [&intermediateCRSAuthCodes](const std::string &first_field,
7736
159k
                                    const std::string &second_field) {
7737
159k
            if (intermediateCRSAuthCodes.empty()) {
7738
159k
                return std::string();
7739
159k
            }
7740
0
            std::string l_sql(" AND (");
7741
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7742
0
                if (i > 0) {
7743
0
                    l_sql += " OR";
7744
0
                }
7745
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7746
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7747
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7748
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7749
0
            }
7750
0
            l_sql += ')';
7751
0
            return l_sql;
7752
159k
        };
7753
7754
39.8k
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7755
39.8k
    for (const auto &pair : intermediateCRSAuthCodes) {
7756
0
        params.emplace_back(pair.first);
7757
0
        params.emplace_back(pair.second);
7758
0
        params.emplace_back(pair.first);
7759
0
        params.emplace_back(pair.second);
7760
0
    }
7761
39.8k
    auto res =
7762
39.8k
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7763
7764
159k
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7765
159k
        std::set<std::pair<std::string, std::string>> setTransf1;
7766
159k
        std::set<std::pair<std::string, std::string>> setTransf2;
7767
159k
        for (const auto &row : resultSet) {
7768
            // table1
7769
23.0k
            const auto &auth_name1 = row[1];
7770
23.0k
            const auto &code1 = row[2];
7771
            // accuracy1
7772
            // table2
7773
23.0k
            const auto &auth_name2 = row[5];
7774
23.0k
            const auto &code2 = row[6];
7775
23.0k
            setTransf1.insert(
7776
23.0k
                std::pair<std::string, std::string>(auth_name1, code1));
7777
23.0k
            setTransf2.insert(
7778
23.0k
                std::pair<std::string, std::string>(auth_name2, code2));
7779
23.0k
        }
7780
159k
        SQLResultSet filteredResultSet;
7781
159k
        for (const auto &row : resultSet) {
7782
23.0k
            const auto &replacement_auth_name1 = row[16];
7783
23.0k
            const auto &replacement_code1 = row[17];
7784
23.0k
            const auto &replacement_auth_name2 = row[18];
7785
23.0k
            const auto &replacement_code2 = row[19];
7786
23.0k
            if (!replacement_auth_name1.empty() &&
7787
74
                setTransf1.find(std::pair<std::string, std::string>(
7788
74
                    replacement_auth_name1, replacement_code1)) !=
7789
74
                    setTransf1.end()) {
7790
                // Skip transformations that are superseded by others that got
7791
                // returned in the result set.
7792
69
                continue;
7793
69
            }
7794
22.9k
            if (!replacement_auth_name2.empty() &&
7795
66
                setTransf2.find(std::pair<std::string, std::string>(
7796
66
                    replacement_auth_name2, replacement_code2)) !=
7797
66
                    setTransf2.end()) {
7798
                // Skip transformations that are superseded by others that got
7799
                // returned in the result set.
7800
63
                continue;
7801
63
            }
7802
22.9k
            filteredResultSet.emplace_back(row);
7803
22.9k
        }
7804
159k
        return filteredResultSet;
7805
159k
    };
7806
7807
39.8k
    if (discardSuperseded) {
7808
39.8k
        res = filterOutSuperseded(std::move(res));
7809
39.8k
    }
7810
7811
39.8k
    const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS,
7812
39.8k
                             &targetCRS](const crs::CRSPtr &intermediateCRS) {
7813
        // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or
7814
        // ITRF>2014
7815
17.7k
        if (ETRFtoETRF && intermediateCRS &&
7816
52
            starts_with(intermediateCRS->nameStr(), "ITRF")) {
7817
51
            const auto normalizeDate = [](int v) {
7818
51
                return (v >= 80 && v <= 99) ? v + 1900 : v;
7819
51
            };
7820
17
            const int srcDate = normalizeDate(
7821
17
                atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7822
17
            const int tgtDate = normalizeDate(
7823
17
                atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7824
17
            const int intermDate = normalizeDate(
7825
17
                atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7826
17
            if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7827
17
                if (intermDate < std::min(srcDate, tgtDate) ||
7828
9
                    intermDate > std::max(srcDate, tgtDate)) {
7829
8
                    return false;
7830
8
                }
7831
17
            }
7832
17
        }
7833
7834
        // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through
7835
        // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464
7836
17.7k
        if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS &&
7837
0
            (intermediateCRS->nameStr() == "NAD83" ||
7838
0
             intermediateCRS->nameStr() == "WGS 84")) {
7839
0
            return false;
7840
0
        }
7841
7842
17.7k
        return true;
7843
17.7k
    };
7844
7845
39.8k
    for (const auto &row : res) {
7846
524
        const auto &table1 = row[0];
7847
524
        const auto &auth_name1 = row[1];
7848
524
        const auto &code1 = row[2];
7849
        // const auto &accuracy1 = row[3];
7850
524
        const auto &table2 = row[4];
7851
524
        const auto &auth_name2 = row[5];
7852
524
        const auto &code2 = row[6];
7853
        // const auto &accuracy2 = row[7];
7854
524
        try {
7855
524
            auto op1 =
7856
524
                d->createFactory(auth_name1)
7857
524
                    ->createCoordinateOperation(
7858
524
                        code1, true, usePROJAlternativeGridNames, table1);
7859
524
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7860
524
                                   targetCRSAuthName, targetCRSCode)) {
7861
0
                continue;
7862
0
            }
7863
524
            if (!checkPivot(op1->targetCRS())) {
7864
0
                continue;
7865
0
            }
7866
524
            auto op2 =
7867
524
                d->createFactory(auth_name2)
7868
524
                    ->createCoordinateOperation(
7869
524
                        code2, true, usePROJAlternativeGridNames, table2);
7870
524
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7871
524
                                   targetCRSAuthName, targetCRSCode)) {
7872
0
                continue;
7873
0
            }
7874
7875
524
            listTmp.emplace_back(
7876
524
                operation::ConcatenatedOperation::createComputeMetadata(
7877
524
                    {std::move(op1), std::move(op2)}, false));
7878
524
        } catch (const std::exception &e) {
7879
            // Mostly for debugging purposes when using an inconsistent
7880
            // database
7881
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7882
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7883
0
            } else {
7884
0
                throw;
7885
0
            }
7886
0
        }
7887
524
    }
7888
7889
    // Case (source->intermediate) and (target->intermediate)
7890
39.8k
    sql = sqlProlog +
7891
39.8k
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
7892
39.8k
          "AND v1.target_crs_code = v2.target_crs_code " +
7893
39.8k
          joinArea +
7894
39.8k
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7895
39.8k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
7896
39.8k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7897
27.9k
        sql += criterionOnIntermediateCRS;
7898
27.9k
    }
7899
39.8k
    intermediateWhere = buildIntermediateWhere("target", "target");
7900
39.8k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7901
39.8k
    if (discardSuperseded) {
7902
39.8k
        res = filterOutSuperseded(std::move(res));
7903
39.8k
    }
7904
7905
39.8k
    for (const auto &row : res) {
7906
16.8k
        const auto &table1 = row[0];
7907
16.8k
        const auto &auth_name1 = row[1];
7908
16.8k
        const auto &code1 = row[2];
7909
        // const auto &accuracy1 = row[3];
7910
16.8k
        const auto &table2 = row[4];
7911
16.8k
        const auto &auth_name2 = row[5];
7912
16.8k
        const auto &code2 = row[6];
7913
        // const auto &accuracy2 = row[7];
7914
16.8k
        try {
7915
16.8k
            auto op1 =
7916
16.8k
                d->createFactory(auth_name1)
7917
16.8k
                    ->createCoordinateOperation(
7918
16.8k
                        code1, true, usePROJAlternativeGridNames, table1);
7919
16.8k
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7920
16.8k
                                   targetCRSAuthName, targetCRSCode)) {
7921
5.14k
                continue;
7922
5.14k
            }
7923
11.7k
            if (!checkPivot(op1->targetCRS())) {
7924
0
                continue;
7925
0
            }
7926
11.7k
            auto op2 =
7927
11.7k
                d->createFactory(auth_name2)
7928
11.7k
                    ->createCoordinateOperation(
7929
11.7k
                        code2, true, usePROJAlternativeGridNames, table2);
7930
11.7k
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7931
11.7k
                                   targetCRSAuthName, targetCRSCode)) {
7932
5.78k
                continue;
7933
5.78k
            }
7934
7935
5.96k
            listTmp.emplace_back(
7936
5.96k
                operation::ConcatenatedOperation::createComputeMetadata(
7937
5.96k
                    {std::move(op1), op2->inverse()}, false));
7938
5.96k
        } catch (const std::exception &e) {
7939
            // Mostly for debugging purposes when using an inconsistent
7940
            // database
7941
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7942
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7943
0
            } else {
7944
0
                throw;
7945
0
            }
7946
0
        }
7947
16.8k
    }
7948
7949
    // Case (intermediate->source) and (intermediate->target)
7950
39.8k
    sql = sqlProlog +
7951
39.8k
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
7952
39.8k
          "AND v1.source_crs_code = v2.source_crs_code " +
7953
39.8k
          joinArea +
7954
39.8k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
7955
39.8k
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
7956
39.8k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7957
27.9k
        if (!minDate.empty()) {
7958
1.62k
            criterionOnIntermediateCRS =
7959
1.62k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7960
1.62k
                "JOIN geodetic_datum y "
7961
1.62k
                "ON "
7962
1.62k
                "y.auth_name = x.datum_auth_name AND "
7963
1.62k
                "y.code = x.datum_code "
7964
1.62k
                "WHERE "
7965
1.62k
                "x.auth_name = v1.source_crs_auth_name AND "
7966
1.62k
                "x.code = v1.source_crs_code AND "
7967
1.62k
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
7968
1.62k
                "(y.publication_date IS NULL OR "
7969
1.62k
                "(y.publication_date >= '" +
7970
1.62k
                minDate + "'))) ";
7971
26.3k
        } else {
7972
26.3k
            criterionOnIntermediateCRS =
7973
26.3k
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7974
26.3k
                "x.auth_name = v1.source_crs_auth_name AND "
7975
26.3k
                "x.code = v1.source_crs_code AND "
7976
26.3k
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7977
26.3k
        }
7978
27.9k
        sql += criterionOnIntermediateCRS;
7979
27.9k
    }
7980
39.8k
    intermediateWhere = buildIntermediateWhere("source", "source");
7981
39.8k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7982
39.8k
    if (discardSuperseded) {
7983
39.8k
        res = filterOutSuperseded(std::move(res));
7984
39.8k
    }
7985
39.8k
    for (const auto &row : res) {
7986
4.35k
        const auto &table1 = row[0];
7987
4.35k
        const auto &auth_name1 = row[1];
7988
4.35k
        const auto &code1 = row[2];
7989
        // const auto &accuracy1 = row[3];
7990
4.35k
        const auto &table2 = row[4];
7991
4.35k
        const auto &auth_name2 = row[5];
7992
4.35k
        const auto &code2 = row[6];
7993
        // const auto &accuracy2 = row[7];
7994
4.35k
        try {
7995
4.35k
            auto op1 =
7996
4.35k
                d->createFactory(auth_name1)
7997
4.35k
                    ->createCoordinateOperation(
7998
4.35k
                        code1, true, usePROJAlternativeGridNames, table1);
7999
4.35k
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8000
4.35k
                                   targetCRSAuthName, targetCRSCode)) {
8001
7
                continue;
8002
7
            }
8003
4.35k
            if (!checkPivot(op1->sourceCRS())) {
8004
8
                continue;
8005
8
            }
8006
4.34k
            auto op2 =
8007
4.34k
                d->createFactory(auth_name2)
8008
4.34k
                    ->createCoordinateOperation(
8009
4.34k
                        code2, true, usePROJAlternativeGridNames, table2);
8010
4.34k
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8011
4.34k
                                   targetCRSAuthName, targetCRSCode)) {
8012
197
                continue;
8013
197
            }
8014
8015
4.14k
            listTmp.emplace_back(
8016
4.14k
                operation::ConcatenatedOperation::createComputeMetadata(
8017
4.14k
                    {op1->inverse(), std::move(op2)}, false));
8018
4.14k
        } catch (const std::exception &e) {
8019
            // Mostly for debugging purposes when using an inconsistent
8020
            // database
8021
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8022
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8023
0
            } else {
8024
0
                throw;
8025
0
            }
8026
0
        }
8027
4.35k
    }
8028
8029
    // Case (intermediate->source) and (target->intermediate)
8030
39.8k
    sql = sqlProlog +
8031
39.8k
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
8032
39.8k
          "AND v1.source_crs_code = v2.target_crs_code " +
8033
39.8k
          joinArea +
8034
39.8k
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
8035
39.8k
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
8036
39.8k
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
8037
27.9k
        sql += criterionOnIntermediateCRS;
8038
27.9k
    }
8039
39.8k
    intermediateWhere = buildIntermediateWhere("source", "target");
8040
39.8k
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
8041
39.8k
    if (discardSuperseded) {
8042
39.8k
        res = filterOutSuperseded(std::move(res));
8043
39.8k
    }
8044
39.8k
    for (const auto &row : res) {
8045
1.15k
        const auto &table1 = row[0];
8046
1.15k
        const auto &auth_name1 = row[1];
8047
1.15k
        const auto &code1 = row[2];
8048
        // const auto &accuracy1 = row[3];
8049
1.15k
        const auto &table2 = row[4];
8050
1.15k
        const auto &auth_name2 = row[5];
8051
1.15k
        const auto &code2 = row[6];
8052
        // const auto &accuracy2 = row[7];
8053
1.15k
        try {
8054
1.15k
            auto op1 =
8055
1.15k
                d->createFactory(auth_name1)
8056
1.15k
                    ->createCoordinateOperation(
8057
1.15k
                        code1, true, usePROJAlternativeGridNames, table1);
8058
1.15k
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
8059
1.15k
                                   targetCRSAuthName, targetCRSCode)) {
8060
0
                continue;
8061
0
            }
8062
1.15k
            if (!checkPivot(op1->sourceCRS())) {
8063
0
                continue;
8064
0
            }
8065
1.15k
            auto op2 =
8066
1.15k
                d->createFactory(auth_name2)
8067
1.15k
                    ->createCoordinateOperation(
8068
1.15k
                        code2, true, usePROJAlternativeGridNames, table2);
8069
1.15k
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8070
1.15k
                                   targetCRSAuthName, targetCRSCode)) {
8071
0
                continue;
8072
0
            }
8073
8074
1.15k
            listTmp.emplace_back(
8075
1.15k
                operation::ConcatenatedOperation::createComputeMetadata(
8076
1.15k
                    {op1->inverse(), op2->inverse()}, false));
8077
1.15k
        } catch (const std::exception &e) {
8078
            // Mostly for debugging purposes when using an inconsistent
8079
            // database
8080
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8081
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8082
0
            } else {
8083
0
                throw;
8084
0
            }
8085
0
        }
8086
1.15k
    }
8087
8088
39.8k
    std::vector<operation::CoordinateOperationNNPtr> list;
8089
39.8k
    for (const auto &op : listTmp) {
8090
11.7k
        if (!discardIfMissingGrid ||
8091
11.7k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8092
1.70k
            list.emplace_back(op);
8093
1.70k
        }
8094
11.7k
    }
8095
8096
39.8k
    return list;
8097
39.8k
}
8098
8099
// ---------------------------------------------------------------------------
8100
8101
//! @cond Doxygen_Suppress
8102
8103
struct TrfmInfo {
8104
    std::string situation{};
8105
    std::string table_name{};
8106
    std::string auth_name{};
8107
    std::string code{};
8108
    std::string name{};
8109
    double west = 0;
8110
    double south = 0;
8111
    double east = 0;
8112
    double north = 0;
8113
};
8114
8115
// ---------------------------------------------------------------------------
8116
8117
std::vector<operation::CoordinateOperationNNPtr>
8118
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
8119
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
8120
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
8121
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
8122
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
8123
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
8124
    const std::vector<std::string> &allowedAuthorities,
8125
    const metadata::ExtentPtr &intersectingExtent1,
8126
988
    const metadata::ExtentPtr &intersectingExtent2) const {
8127
8128
988
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
8129
8130
988
    if (sourceCRSAuthName == targetCRSAuthName &&
8131
773
        sourceCRSCode == targetCRSCode) {
8132
0
        return listTmp;
8133
0
    }
8134
988
    const auto sourceGeodCRS =
8135
988
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
8136
988
    const auto targetGeodCRS =
8137
988
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
8138
988
    if (!sourceGeodCRS || !targetGeodCRS) {
8139
0
        return listTmp;
8140
0
    }
8141
8142
988
    const bool NAD83_CSRS_to_NAD83_CSRS =
8143
988
        starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") &&
8144
23
        starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)");
8145
8146
988
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
8147
988
                                                const std::string &crsAuthName,
8148
1.97k
                                                const std::string &crsCode) {
8149
        // Find all geodetic CRS that share the same datum as the CRS
8150
1.97k
        SQLResultSet listCRS;
8151
8152
1.97k
        const common::IdentifiedObject *obj = crs->datum().get();
8153
1.97k
        if (obj == nullptr)
8154
596
            obj = crs->datumEnsemble().get();
8155
1.97k
        assert(obj != nullptr);
8156
1.97k
        const auto &ids = obj->identifiers();
8157
1.97k
        std::string datumAuthName;
8158
1.97k
        std::string datumCode;
8159
1.97k
        if (!ids.empty()) {
8160
1.97k
            const auto &id = ids.front();
8161
1.97k
            datumAuthName = *(id->codeSpace());
8162
1.97k
            datumCode = id->code();
8163
1.97k
        } else {
8164
0
            const auto res =
8165
0
                d->run("SELECT datum_auth_name, datum_code FROM "
8166
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
8167
0
                       {crsAuthName, crsCode});
8168
0
            if (res.size() != 1) {
8169
0
                return listCRS;
8170
0
            }
8171
0
            const auto &row = res.front();
8172
0
            datumAuthName = row[0];
8173
0
            datumCode = row[1];
8174
0
        }
8175
8176
1.97k
        listCRS =
8177
1.97k
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
8178
1.97k
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
8179
1.97k
                   {datumAuthName, datumCode});
8180
1.97k
        if (listCRS.empty()) {
8181
            // Can happen if the CRS is deprecated
8182
2
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
8183
2
        }
8184
1.97k
        return listCRS;
8185
1.97k
    };
8186
8187
988
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
8188
988
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
8189
988
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
8190
988
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
8191
988
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
8192
        // would happen only if we had CRS objects in the database without a
8193
        // link to a datum.
8194
0
        return listTmp;
8195
0
    }
8196
8197
988
    ListOfParams params;
8198
988
    const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS,
8199
988
                               &allowedAuthorities, &params, &listSourceCRS,
8200
988
                               &listTargetCRS](bool isSourceCRS,
8201
3.95k
                                               bool selectOnTarget) {
8202
3.95k
        std::string situation;
8203
3.95k
        if (isSourceCRS)
8204
1.97k
            situation = "src";
8205
1.97k
        else
8206
1.97k
            situation = "tgt";
8207
3.95k
        if (selectOnTarget)
8208
1.97k
            situation += "_is_tgt";
8209
1.97k
        else
8210
1.97k
            situation += "_is_src";
8211
3.95k
        const std::string prefix1(selectOnTarget ? "source" : "target");
8212
3.95k
        const std::string prefix2(selectOnTarget ? "target" : "source");
8213
3.95k
        std::string sql("SELECT '");
8214
3.95k
        sql += situation;
8215
3.95k
        sql += "' as situation, v.table_name, v.auth_name, "
8216
3.95k
               "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
8217
3.95k
               "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
8218
3.95k
               "FROM coordinate_operation_view v "
8219
3.95k
               "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
8220
3.95k
        sql += prefix1;
8221
3.95k
        sql += "_crs_auth_name AND gcrs.code = ";
8222
3.95k
        sql += prefix1;
8223
3.95k
        sql += "_crs_code "
8224
8225
3.95k
               "LEFT JOIN usage u ON "
8226
3.95k
               "u.object_table_name = v.table_name AND "
8227
3.95k
               "u.object_auth_name = v.auth_name AND "
8228
3.95k
               "u.object_code = v.code "
8229
3.95k
               "LEFT JOIN extent a "
8230
3.95k
               "ON a.auth_name = u.extent_auth_name AND "
8231
3.95k
               "a.code = u.extent_code "
8232
3.95k
               "WHERE v.deprecated = 0 AND (";
8233
8234
3.95k
        std::string cond;
8235
8236
3.95k
        const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
8237
23.5k
        for (const auto &row : list) {
8238
23.5k
            if (!cond.empty())
8239
19.6k
                cond += " OR ";
8240
23.5k
            cond += '(';
8241
23.5k
            cond += prefix2;
8242
23.5k
            cond += "_crs_auth_name = ? AND ";
8243
23.5k
            cond += prefix2;
8244
23.5k
            cond += "_crs_code = ?)";
8245
23.5k
            params.emplace_back(row[0]);
8246
23.5k
            params.emplace_back(row[1]);
8247
23.5k
        }
8248
8249
3.95k
        sql += cond;
8250
3.95k
        sql += ") ";
8251
8252
3.95k
        if (!allowedAuthorities.empty()) {
8253
3.86k
            sql += "AND v.auth_name IN (";
8254
15.4k
            for (size_t i = 0; i < allowedAuthorities.size(); i++) {
8255
11.6k
                if (i > 0)
8256
7.73k
                    sql += ',';
8257
11.6k
                sql += '?';
8258
11.6k
            }
8259
3.86k
            sql += ") ";
8260
11.6k
            for (const auto &allowedAuthority : allowedAuthorities) {
8261
11.6k
                params.emplace_back(allowedAuthority);
8262
11.6k
            }
8263
3.86k
        }
8264
3.95k
        if (d->hasAuthorityRestriction()) {
8265
0
            sql += "AND v.auth_name = ? ";
8266
0
            params.emplace_back(d->authority());
8267
0
        }
8268
3.95k
        if (NAD83_CSRS_to_NAD83_CSRS) {
8269
            // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go
8270
            // through NAD83 generic. Cf
8271
            // https://github.com/OSGeo/PROJ/issues/4464
8272
8
            sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') ";
8273
8
        }
8274
8275
3.95k
        return sql;
8276
3.95k
    };
8277
8278
988
    std::string sql(BuildSQLPart(true, true));
8279
988
    sql += "UNION ALL ";
8280
988
    sql += BuildSQLPart(false, true);
8281
988
    sql += "UNION ALL ";
8282
988
    sql += BuildSQLPart(true, false);
8283
988
    sql += "UNION ALL ";
8284
988
    sql += BuildSQLPart(false, false);
8285
    // fprintf(stderr, "sql : %s\n", sql.c_str());
8286
8287
    // Find all operations that have as source/target CRS a CRS that
8288
    // share the same datum as the source or targetCRS
8289
988
    const auto res = d->run(sql, params);
8290
8291
988
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
8292
988
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
8293
8294
794k
    for (const auto &row : res) {
8295
794k
        try {
8296
794k
            TrfmInfo trfm;
8297
794k
            trfm.situation = row[0];
8298
794k
            trfm.table_name = row[1];
8299
794k
            trfm.auth_name = row[2];
8300
794k
            trfm.code = row[3];
8301
794k
            trfm.name = row[4];
8302
794k
            const auto &datum_auth_name = row[5];
8303
794k
            const auto &datum_code = row[6];
8304
794k
            trfm.west = c_locale_stod(row[7]);
8305
794k
            trfm.south = c_locale_stod(row[8]);
8306
794k
            trfm.east = c_locale_stod(row[9]);
8307
794k
            trfm.north = c_locale_stod(row[10]);
8308
794k
            const std::string key =
8309
794k
                std::string(datum_auth_name).append(":").append(datum_code);
8310
794k
            if (trfm.situation == "src_is_tgt" ||
8311
60.9k
                trfm.situation == "src_is_src")
8312
755k
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
8313
38.9k
            else
8314
38.9k
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
8315
794k
        } catch (const std::exception &) {
8316
0
        }
8317
794k
    }
8318
8319
988
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
8320
1.97k
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
8321
1.97k
        if (extent) {
8322
1.12k
            const auto &geogExtent = extent->geographicElements();
8323
1.12k
            if (geogExtent.size() == 1) {
8324
1.12k
                auto bbox =
8325
1.12k
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
8326
1.12k
                        geogExtent[0].get());
8327
1.12k
                if (bbox) {
8328
1.12k
                    const double south_lat = bbox->southBoundLatitude();
8329
1.12k
                    const double west_lon = bbox->westBoundLongitude();
8330
1.12k
                    const double north_lat = bbox->northBoundLatitude();
8331
1.12k
                    const double east_lon = bbox->eastBoundLongitude();
8332
1.12k
                    if (south_lat != -90.0 || west_lon != -180.0 ||
8333
577
                        north_lat != 90.0 || east_lon != 180.0) {
8334
552
                        extraBbox.emplace_back(bbox);
8335
552
                    }
8336
1.12k
                }
8337
1.12k
            }
8338
1.12k
        }
8339
1.97k
    }
8340
8341
988
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
8342
988
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
8343
988
    std::map<std::string, TrfmInfo> setOfTransformations;
8344
8345
70.2k
    const auto MakeKey = [](const TrfmInfo &trfm) {
8346
70.2k
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
8347
70.2k
    };
8348
8349
    // Find transformations that share a pivot datum, and do bbox filtering
8350
299k
    for (const auto &kvSource : mapIntermDatumOfSource) {
8351
299k
        const auto &listTrmfSource = kvSource.second;
8352
299k
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
8353
299k
        if (iter == mapIntermDatumOfTarget.end())
8354
295k
            continue;
8355
8356
4.75k
        const auto &listTrfmTarget = iter->second;
8357
29.0k
        for (const auto &trfmSource : listTrmfSource) {
8358
29.0k
            auto bbox1 = metadata::GeographicBoundingBox::create(
8359
29.0k
                trfmSource.west, trfmSource.south, trfmSource.east,
8360
29.0k
                trfmSource.north);
8361
29.0k
            bool okBbox1 = true;
8362
29.0k
            for (const auto bbox : extraBbox)
8363
11.5k
                okBbox1 &= bbox->intersects(bbox1);
8364
29.0k
            if (!okBbox1)
8365
5.48k
                continue;
8366
8367
23.5k
            const std::string key1 = MakeKey(trfmSource);
8368
8369
56.7k
            for (const auto &trfmTarget : listTrfmTarget) {
8370
56.7k
                auto bbox2 = metadata::GeographicBoundingBox::create(
8371
56.7k
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
8372
56.7k
                    trfmTarget.north);
8373
56.7k
                if (!bbox1->intersects(bbox2))
8374
40.4k
                    continue;
8375
16.2k
                bool okBbox2 = true;
8376
16.2k
                for (const auto bbox : extraBbox)
8377
3.79k
                    okBbox2 &= bbox->intersects(bbox2);
8378
16.2k
                if (!okBbox2)
8379
624
                    continue;
8380
8381
15.6k
                operation::CoordinateOperationPtr op1;
8382
15.6k
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
8383
10.2k
                    auto op1NN = d->createFactory(trfmSource.auth_name)
8384
10.2k
                                     ->createCoordinateOperation(
8385
10.2k
                                         trfmSource.code, true,
8386
10.2k
                                         usePROJAlternativeGridNames,
8387
10.2k
                                         trfmSource.table_name);
8388
10.2k
                    op1 = op1NN.as_nullable();
8389
10.2k
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
8390
10.2k
                                           sourceCRSCode, targetCRSAuthName,
8391
10.2k
                                           targetCRSCode)) {
8392
6
                        op1.reset();
8393
6
                    }
8394
10.2k
                    oMapTrfmKeyToOp[key1] = op1;
8395
10.2k
                } else {
8396
5.43k
                    op1 = oMapTrfmKeyToOp[key1];
8397
5.43k
                }
8398
15.6k
                if (op1 == nullptr)
8399
6
                    continue;
8400
8401
15.6k
                const std::string key2 = MakeKey(trfmTarget);
8402
8403
15.6k
                operation::CoordinateOperationPtr op2;
8404
15.6k
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
8405
7.07k
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
8406
7.07k
                                     ->createCoordinateOperation(
8407
7.07k
                                         trfmTarget.code, true,
8408
7.07k
                                         usePROJAlternativeGridNames,
8409
7.07k
                                         trfmTarget.table_name);
8410
7.07k
                    op2 = op2NN.as_nullable();
8411
7.07k
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
8412
7.07k
                                           sourceCRSCode, targetCRSAuthName,
8413
7.07k
                                           targetCRSCode)) {
8414
27
                        op2.reset();
8415
27
                    }
8416
7.07k
                    oMapTrfmKeyToOp[key2] = op2;
8417
8.58k
                } else {
8418
8.58k
                    op2 = oMapTrfmKeyToOp[key2];
8419
8.58k
                }
8420
15.6k
                if (op2 == nullptr)
8421
154
                    continue;
8422
8423
15.5k
                candidates.emplace_back(
8424
15.5k
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
8425
15.5k
                setOfTransformations[key1] = trfmSource;
8426
15.5k
                setOfTransformations[key2] = trfmTarget;
8427
15.5k
            }
8428
23.5k
        }
8429
4.75k
    }
8430
8431
988
    std::set<std::string> setSuperseded;
8432
988
    if (discardSuperseded && !setOfTransformations.empty()) {
8433
588
        std::string findSupersededSql(
8434
588
            "SELECT superseded_table_name, "
8435
588
            "superseded_auth_name, superseded_code, "
8436
588
            "replacement_auth_name, replacement_code "
8437
588
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
8438
588
        bool findSupersededFirstWhere = true;
8439
588
        ListOfParams findSupersededParams;
8440
8441
588
        const auto keyMapSupersession = [](const std::string &table_name,
8442
588
                                           const std::string &auth_name,
8443
18.2k
                                           const std::string &code) {
8444
18.2k
            return table_name + auth_name + code;
8445
18.2k
        };
8446
8447
588
        std::set<std::pair<std::string, std::string>> setTransf;
8448
17.1k
        for (const auto &kv : setOfTransformations) {
8449
17.1k
            const auto &table = kv.second.table_name;
8450
17.1k
            const auto &auth_name = kv.second.auth_name;
8451
17.1k
            const auto &code = kv.second.code;
8452
8453
17.1k
            if (!findSupersededFirstWhere)
8454
16.5k
                findSupersededSql += " OR ";
8455
17.1k
            findSupersededFirstWhere = false;
8456
17.1k
            findSupersededSql +=
8457
17.1k
                "(superseded_table_name = ? AND replacement_table_name = "
8458
17.1k
                "superseded_table_name AND superseded_auth_name = ? AND "
8459
17.1k
                "superseded_code = ?)";
8460
17.1k
            findSupersededParams.push_back(table);
8461
17.1k
            findSupersededParams.push_back(auth_name);
8462
17.1k
            findSupersededParams.push_back(code);
8463
8464
17.1k
            setTransf.insert(
8465
17.1k
                std::pair<std::string, std::string>(auth_name, code));
8466
17.1k
        }
8467
588
        findSupersededSql += ')';
8468
8469
588
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
8470
588
            mapSupersession;
8471
8472
588
        const auto resSuperseded =
8473
588
            d->run(findSupersededSql, findSupersededParams);
8474
1.11k
        for (const auto &row : resSuperseded) {
8475
1.11k
            const auto &superseded_table_name = row[0];
8476
1.11k
            const auto &superseded_auth_name = row[1];
8477
1.11k
            const auto &superseded_code = row[2];
8478
1.11k
            const auto &replacement_auth_name = row[3];
8479
1.11k
            const auto &replacement_code = row[4];
8480
1.11k
            mapSupersession[keyMapSupersession(superseded_table_name,
8481
1.11k
                                               superseded_auth_name,
8482
1.11k
                                               superseded_code)]
8483
1.11k
                .push_back(std::pair<std::string, std::string>(
8484
1.11k
                    replacement_auth_name, replacement_code));
8485
1.11k
        }
8486
8487
17.1k
        for (const auto &kv : setOfTransformations) {
8488
17.1k
            const auto &table = kv.second.table_name;
8489
17.1k
            const auto &auth_name = kv.second.auth_name;
8490
17.1k
            const auto &code = kv.second.code;
8491
8492
17.1k
            const auto iter = mapSupersession.find(
8493
17.1k
                keyMapSupersession(table, auth_name, code));
8494
17.1k
            if (iter != mapSupersession.end()) {
8495
1.11k
                bool foundReplacement = false;
8496
1.11k
                for (const auto &replacement : iter->second) {
8497
1.11k
                    const auto &replacement_auth_name = replacement.first;
8498
1.11k
                    const auto &replacement_code = replacement.second;
8499
1.11k
                    if (setTransf.find(std::pair<std::string, std::string>(
8500
1.11k
                            replacement_auth_name, replacement_code)) !=
8501
1.11k
                        setTransf.end()) {
8502
                        // Skip transformations that are superseded by others
8503
                        // that got
8504
                        // returned in the result set.
8505
1.11k
                        foundReplacement = true;
8506
1.11k
                        break;
8507
1.11k
                    }
8508
1.11k
                }
8509
1.11k
                if (foundReplacement) {
8510
1.11k
                    setSuperseded.insert(kv.first);
8511
1.11k
                }
8512
1.11k
            }
8513
17.1k
        }
8514
588
    }
8515
8516
988
    std::string sourceDatumPubDate;
8517
988
    const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context());
8518
988
    if (sourceDatum->publicationDate().has_value()) {
8519
294
        sourceDatumPubDate = sourceDatum->publicationDate()->toString();
8520
294
    }
8521
8522
988
    std::string targetDatumPubDate;
8523
988
    const auto targetDatum = targetGeodCRS->datumNonNull(d->context());
8524
988
    if (targetDatum->publicationDate().has_value()) {
8525
924
        targetDatumPubDate = targetDatum->publicationDate()->toString();
8526
924
    }
8527
8528
988
    const std::string mostAncientDatumPubDate =
8529
988
        (!targetDatumPubDate.empty() &&
8530
924
         (sourceDatumPubDate.empty() ||
8531
251
          targetDatumPubDate < sourceDatumPubDate))
8532
988
            ? targetDatumPubDate
8533
988
            : sourceDatumPubDate;
8534
8535
988
    auto opFactory = operation::CoordinateOperationFactory::create();
8536
15.5k
    for (const auto &pair : candidates) {
8537
15.5k
        const auto &trfmSource = pair.first;
8538
15.5k
        const auto &trfmTarget = pair.second;
8539
15.5k
        const std::string key1 = MakeKey(trfmSource);
8540
15.5k
        const std::string key2 = MakeKey(trfmTarget);
8541
15.5k
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8542
15.4k
            setSuperseded.find(key2) != setSuperseded.end()) {
8543
1.20k
            continue;
8544
1.20k
        }
8545
14.3k
        auto op1 = oMapTrfmKeyToOp[key1];
8546
14.3k
        auto op2 = oMapTrfmKeyToOp[key2];
8547
14.3k
        auto op1NN = NN_NO_CHECK(op1);
8548
14.3k
        auto op2NN = NN_NO_CHECK(op2);
8549
14.3k
        if (trfmSource.situation == "src_is_tgt")
8550
12.7k
            op1NN = op1NN->inverse();
8551
14.3k
        if (trfmTarget.situation == "tgt_is_src")
8552
6.70k
            op2NN = op2NN->inverse();
8553
8554
14.3k
        const auto &op1Source = op1NN->sourceCRS();
8555
14.3k
        const auto &op1Target = op1NN->targetCRS();
8556
14.3k
        const auto &op2Source = op2NN->sourceCRS();
8557
14.3k
        const auto &op2Target = op2NN->targetCRS();
8558
14.3k
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8559
0
            continue;
8560
0
        }
8561
8562
        // Skip operations using a datum that is older than the source or
8563
        // target datum (e.g to avoid ED50 to WGS84 to go through NTF)
8564
14.3k
        if (!mostAncientDatumPubDate.empty()) {
8565
13.7k
            const auto isOlderCRS = [this, &mostAncientDatumPubDate](
8566
54.9k
                                        const crs::CRSPtr &crs) {
8567
54.9k
                const auto geogCRS =
8568
54.9k
                    dynamic_cast<const crs::GeodeticCRS *>(crs.get());
8569
54.9k
                if (geogCRS) {
8570
54.9k
                    const auto datum = geogCRS->datumNonNull(d->context());
8571
                    // Hum, theoretically we'd want to check
8572
                    // datum->publicationDate()->toString() <
8573
                    // mostAncientDatumPubDate but that would exclude doing
8574
                    // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008,
8575
                    // since IG05/12 Intermediate CRS has been published later
8576
                    // than ITRF2008. So use a cut of date for ancient vs
8577
                    // "modern" era.
8578
54.9k
                    constexpr const char *CUT_OFF_DATE = "1900-01-01";
8579
54.9k
                    if (datum->publicationDate().has_value() &&
8580
38.0k
                        datum->publicationDate()->toString() < CUT_OFF_DATE &&
8581
0
                        mostAncientDatumPubDate > CUT_OFF_DATE) {
8582
0
                        return true;
8583
0
                    }
8584
54.9k
                }
8585
54.9k
                return false;
8586
54.9k
            };
8587
8588
13.7k
            if (isOlderCRS(op1Source) || isOlderCRS(op1Target) ||
8589
13.7k
                isOlderCRS(op2Source) || isOlderCRS(op2Target))
8590
0
                continue;
8591
13.7k
        }
8592
8593
14.3k
        std::vector<operation::CoordinateOperationNNPtr> steps;
8594
8595
14.3k
        if (!sourceCRS->isEquivalentTo(
8596
14.3k
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8597
7.22k
            auto opFirst =
8598
7.22k
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8599
7.22k
            assert(opFirst);
8600
7.22k
            steps.emplace_back(NN_NO_CHECK(opFirst));
8601
7.22k
        }
8602
8603
14.3k
        steps.emplace_back(op1NN);
8604
8605
14.3k
        if (!op1Target->isEquivalentTo(
8606
14.3k
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8607
5.87k
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8608
5.87k
                                                       NN_NO_CHECK(op2Source));
8609
5.87k
            assert(opMiddle);
8610
5.87k
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8611
5.87k
        }
8612
8613
14.3k
        steps.emplace_back(op2NN);
8614
8615
14.3k
        if (!op2Target->isEquivalentTo(
8616
14.3k
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8617
11.5k
            auto opLast =
8618
11.5k
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8619
11.5k
            assert(opLast);
8620
11.5k
            steps.emplace_back(NN_NO_CHECK(opLast));
8621
11.5k
        }
8622
8623
14.3k
        listTmp.emplace_back(
8624
14.3k
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8625
14.3k
                                                                    false));
8626
14.3k
    }
8627
8628
988
    std::vector<operation::CoordinateOperationNNPtr> list;
8629
14.3k
    for (const auto &op : listTmp) {
8630
14.3k
        if (!discardIfMissingGrid ||
8631
14.3k
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8632
3.66k
            list.emplace_back(op);
8633
3.66k
        }
8634
14.3k
    }
8635
8636
988
    return list;
8637
988
}
8638
8639
//! @endcond
8640
8641
// ---------------------------------------------------------------------------
8642
8643
/** \brief Returns the authority name associated to this factory.
8644
 * @return name.
8645
 */
8646
261k
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8647
261k
    return d->authority();
8648
261k
}
8649
8650
// ---------------------------------------------------------------------------
8651
8652
/** \brief Returns the set of authority codes of the given object type.
8653
 *
8654
 * @param type Object type.
8655
 * @param allowDeprecated whether we should return deprecated objects as well.
8656
 * @return the set of authority codes for spatial reference objects of the given
8657
 * type
8658
 * @throw FactoryException in case of error.
8659
 */
8660
std::set<std::string>
8661
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8662
0
                                    bool allowDeprecated) const {
8663
0
    std::string sql;
8664
0
    switch (type) {
8665
0
    case ObjectType::PRIME_MERIDIAN:
8666
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8667
0
        break;
8668
0
    case ObjectType::ELLIPSOID:
8669
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8670
0
        break;
8671
0
    case ObjectType::DATUM:
8672
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8673
0
              "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND ";
8674
0
        break;
8675
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8676
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8677
0
        break;
8678
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8679
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8680
0
              "frame_reference_epoch IS NOT NULL AND ";
8681
0
        break;
8682
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8683
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8684
0
        break;
8685
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8686
0
        sql = "SELECT code FROM vertical_datum WHERE "
8687
0
              "frame_reference_epoch IS NOT NULL AND ";
8688
0
        break;
8689
0
    case ObjectType::ENGINEERING_DATUM:
8690
0
        sql = "SELECT code FROM engineering_datum WHERE ";
8691
0
        break;
8692
0
    case ObjectType::CRS:
8693
0
        sql = "SELECT code FROM crs_view WHERE ";
8694
0
        break;
8695
0
    case ObjectType::GEODETIC_CRS:
8696
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8697
0
        break;
8698
0
    case ObjectType::GEOCENTRIC_CRS:
8699
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8700
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8701
0
        break;
8702
0
    case ObjectType::GEOGRAPHIC_CRS:
8703
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8704
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8705
0
        break;
8706
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8707
0
        sql =
8708
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8709
0
            " AND ";
8710
0
        break;
8711
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8712
0
        sql =
8713
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8714
0
            " AND ";
8715
0
        break;
8716
0
    case ObjectType::VERTICAL_CRS:
8717
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8718
0
        break;
8719
0
    case ObjectType::PROJECTED_CRS:
8720
0
        sql = "SELECT code FROM projected_crs WHERE ";
8721
0
        break;
8722
0
    case ObjectType::COMPOUND_CRS:
8723
0
        sql = "SELECT code FROM compound_crs WHERE ";
8724
0
        break;
8725
0
    case ObjectType::ENGINEERING_CRS:
8726
0
        sql = "SELECT code FROM engineering_crs WHERE ";
8727
0
        break;
8728
0
    case ObjectType::COORDINATE_OPERATION:
8729
0
        sql =
8730
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8731
0
        break;
8732
0
    case ObjectType::CONVERSION:
8733
0
        sql = "SELECT code FROM conversion WHERE ";
8734
0
        break;
8735
0
    case ObjectType::TRANSFORMATION:
8736
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8737
0
              "'concatenated_operation' AND ";
8738
0
        break;
8739
0
    case ObjectType::CONCATENATED_OPERATION:
8740
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8741
0
        break;
8742
0
    case ObjectType::DATUM_ENSEMBLE:
8743
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8744
0
              "('geodetic_datum', 'vertical_datum') AND "
8745
0
              "type = 'ensemble' AND ";
8746
0
        break;
8747
0
    }
8748
8749
0
    sql += "auth_name = ?";
8750
0
    if (!allowDeprecated) {
8751
0
        sql += " AND deprecated = 0";
8752
0
    }
8753
8754
0
    auto res = d->run(sql, {d->authority()});
8755
0
    std::set<std::string> set;
8756
0
    for (const auto &row : res) {
8757
0
        set.insert(row[0]);
8758
0
    }
8759
0
    return set;
8760
0
}
8761
8762
// ---------------------------------------------------------------------------
8763
8764
/** \brief Gets a description of the object corresponding to a code.
8765
 *
8766
 * \note In case of several objects of different types with the same code,
8767
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8768
 * will be selected.
8769
 *
8770
 * @param code Object code allocated by authority. (e.g. "4326")
8771
 * @return description.
8772
 * @throw NoSuchAuthorityCodeException if there is no matching object.
8773
 * @throw FactoryException in case of other errors.
8774
 */
8775
std::string
8776
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8777
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8778
0
               "AND code = ? ORDER BY table_name";
8779
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8780
0
    if (sqlRes.empty()) {
8781
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8782
0
                                           code);
8783
0
    }
8784
0
    std::string text;
8785
0
    for (const auto &row : sqlRes) {
8786
0
        const auto &tableName = row[1];
8787
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8788
0
            tableName == "vertical_crs" || tableName == "compound_crs" ||
8789
0
            tableName == "engineering_crs") {
8790
0
            return row[0];
8791
0
        } else if (text.empty()) {
8792
0
            text = row[0];
8793
0
        }
8794
0
    }
8795
0
    return text;
8796
0
}
8797
8798
// ---------------------------------------------------------------------------
8799
8800
/** \brief Return a list of information on CRS objects
8801
 *
8802
 * This is functionally equivalent of listing the codes from an authority,
8803
 * instantiating
8804
 * a CRS object for each of them and getting the information from this CRS
8805
 * object, but this implementation has much less overhead.
8806
 *
8807
 * @throw FactoryException in case of error.
8808
 */
8809
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8810
8811
0
    const auto getSqlArea = [](const char *table_name) {
8812
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8813
0
        sql += table_name;
8814
0
        sql += "' AND "
8815
0
               "u.object_auth_name = c.auth_name AND "
8816
0
               "u.object_code = c.code "
8817
0
               "LEFT JOIN extent a "
8818
0
               "ON a.auth_name = u.extent_auth_name AND "
8819
0
               "a.code = u.extent_code ";
8820
0
        return sql;
8821
0
    };
8822
8823
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8824
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8825
0
        sql += crs_alias;
8826
0
        sql += ".datum_auth_name AND gd.code = ";
8827
0
        sql += crs_alias;
8828
0
        sql += ".datum_code "
8829
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8830
0
               "AND e.code = gd.ellipsoid_code "
8831
0
               "LEFT JOIN celestial_body cb ON "
8832
0
               "cb.auth_name = e.celestial_body_auth_name "
8833
0
               "AND cb.code = e.celestial_body_code ";
8834
0
        return sql;
8835
0
    };
8836
8837
0
    std::string sql = "SELECT * FROM ("
8838
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8839
0
                      "c.deprecated, "
8840
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8841
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8842
0
    sql += getSqlArea("geodetic_crs");
8843
0
    sql += getJoinCelestialBody("c");
8844
0
    ListOfParams params;
8845
0
    if (d->hasAuthorityRestriction()) {
8846
0
        sql += "WHERE c.auth_name = ? ";
8847
0
        params.emplace_back(d->authority());
8848
0
    }
8849
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8850
0
           "c.deprecated, "
8851
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8852
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8853
0
           "projected_crs c "
8854
0
           "LEFT JOIN conversion_table conv ON "
8855
0
           "c.conversion_auth_name = conv.auth_name AND "
8856
0
           "c.conversion_code = conv.code "
8857
0
           "LEFT JOIN conversion_method cm ON "
8858
0
           "conv.method_auth_name = cm.auth_name AND "
8859
0
           "conv.method_code = cm.code "
8860
0
           "LEFT JOIN geodetic_crs gcrs ON "
8861
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
8862
0
           "AND gcrs.code = c.geodetic_crs_code ";
8863
0
    sql += getSqlArea("projected_crs");
8864
0
    sql += getJoinCelestialBody("gcrs");
8865
0
    if (d->hasAuthorityRestriction()) {
8866
0
        sql += "WHERE c.auth_name = ? ";
8867
0
        params.emplace_back(d->authority());
8868
0
    }
8869
    // FIXME: we can't handle non-EARTH vertical CRS for now
8870
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
8871
0
           "c.deprecated, "
8872
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8873
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
8874
0
    sql += getSqlArea("vertical_crs");
8875
0
    if (d->hasAuthorityRestriction()) {
8876
0
        sql += "WHERE c.auth_name = ? ";
8877
0
        params.emplace_back(d->authority());
8878
0
    }
8879
    // FIXME: we can't handle non-EARTH compound CRS for now
8880
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
8881
0
           "c.deprecated, "
8882
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8883
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
8884
0
    sql += getSqlArea("compound_crs");
8885
0
    if (d->hasAuthorityRestriction()) {
8886
0
        sql += "WHERE c.auth_name = ? ";
8887
0
        params.emplace_back(d->authority());
8888
0
    }
8889
    // FIXME: we can't handle non-EARTH compound CRS for now
8890
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', "
8891
0
           "c.deprecated, "
8892
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8893
0
           "a.description, NULL, 'Earth' FROM engineering_crs c ";
8894
0
    sql += getSqlArea("engineering_crs");
8895
0
    if (d->hasAuthorityRestriction()) {
8896
0
        sql += "WHERE c.auth_name = ? ";
8897
0
        params.emplace_back(d->authority());
8898
0
    }
8899
0
    sql += ") r ORDER BY auth_name, code";
8900
0
    auto sqlRes = d->run(sql, params);
8901
0
    std::list<AuthorityFactory::CRSInfo> res;
8902
0
    for (const auto &row : sqlRes) {
8903
0
        AuthorityFactory::CRSInfo info;
8904
0
        info.authName = row[0];
8905
0
        info.code = row[1];
8906
0
        info.name = row[2];
8907
0
        const auto &type = row[3];
8908
0
        if (type == GEOG_2D) {
8909
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
8910
0
        } else if (type == GEOG_3D) {
8911
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
8912
0
        } else if (type == GEOCENTRIC) {
8913
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
8914
0
        } else if (type == OTHER) {
8915
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
8916
0
        } else if (type == PROJECTED) {
8917
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
8918
0
        } else if (type == VERTICAL) {
8919
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
8920
0
        } else if (type == COMPOUND) {
8921
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
8922
0
        } else if (type == ENGINEERING) {
8923
0
            info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS;
8924
0
        }
8925
0
        info.deprecated = row[4] == "1";
8926
0
        if (row[5].empty()) {
8927
0
            info.bbox_valid = false;
8928
0
        } else {
8929
0
            info.bbox_valid = true;
8930
0
            info.west_lon_degree = c_locale_stod(row[5]);
8931
0
            info.south_lat_degree = c_locale_stod(row[6]);
8932
0
            info.east_lon_degree = c_locale_stod(row[7]);
8933
0
            info.north_lat_degree = c_locale_stod(row[8]);
8934
0
        }
8935
0
        info.areaName = row[9];
8936
0
        info.projectionMethodName = row[10];
8937
0
        info.celestialBodyName = row[11];
8938
0
        res.emplace_back(info);
8939
0
    }
8940
0
    return res;
8941
0
}
8942
8943
// ---------------------------------------------------------------------------
8944
8945
//! @cond Doxygen_Suppress
8946
AuthorityFactory::UnitInfo::UnitInfo()
8947
0
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
8948
0
      deprecated{} {}
8949
//! @endcond
8950
8951
// ---------------------------------------------------------------------------
8952
8953
//! @cond Doxygen_Suppress
8954
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
8955
//! @endcond
8956
8957
// ---------------------------------------------------------------------------
8958
8959
/** \brief Return the list of units.
8960
 * @throw FactoryException in case of error.
8961
 *
8962
 * @since 7.1
8963
 */
8964
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
8965
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
8966
0
                      "proj_short_name, deprecated FROM unit_of_measure";
8967
0
    ListOfParams params;
8968
0
    if (d->hasAuthorityRestriction()) {
8969
0
        sql += " WHERE auth_name = ?";
8970
0
        params.emplace_back(d->authority());
8971
0
    }
8972
0
    sql += " ORDER BY auth_name, code";
8973
8974
0
    auto sqlRes = d->run(sql, params);
8975
0
    std::list<AuthorityFactory::UnitInfo> res;
8976
0
    for (const auto &row : sqlRes) {
8977
0
        AuthorityFactory::UnitInfo info;
8978
0
        info.authName = row[0];
8979
0
        info.code = row[1];
8980
0
        info.name = row[2];
8981
0
        const std::string &raw_category(row[3]);
8982
0
        if (raw_category == "length") {
8983
0
            info.category = info.name.find(" per ") != std::string::npos
8984
0
                                ? "linear_per_time"
8985
0
                                : "linear";
8986
0
        } else if (raw_category == "angle") {
8987
0
            info.category = info.name.find(" per ") != std::string::npos
8988
0
                                ? "angular_per_time"
8989
0
                                : "angular";
8990
0
        } else if (raw_category == "scale") {
8991
0
            info.category =
8992
0
                info.name.find(" per year") != std::string::npos ||
8993
0
                        info.name.find(" per second") != std::string::npos
8994
0
                    ? "scale_per_time"
8995
0
                    : "scale";
8996
0
        } else {
8997
0
            info.category = raw_category;
8998
0
        }
8999
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
9000
0
        info.projShortName = row[5];
9001
0
        info.deprecated = row[6] == "1";
9002
0
        res.emplace_back(info);
9003
0
    }
9004
0
    return res;
9005
0
}
9006
9007
// ---------------------------------------------------------------------------
9008
9009
/** \brief Return the list of celestial bodies.
9010
 * @throw FactoryException in case of error.
9011
 *
9012
 * @since 8.1
9013
 */
9014
std::list<AuthorityFactory::CelestialBodyInfo>
9015
0
AuthorityFactory::getCelestialBodyList() const {
9016
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
9017
0
    ListOfParams params;
9018
0
    if (d->hasAuthorityRestriction()) {
9019
0
        sql += " WHERE auth_name = ?";
9020
0
        params.emplace_back(d->authority());
9021
0
    }
9022
0
    sql += " ORDER BY auth_name, name";
9023
9024
0
    auto sqlRes = d->run(sql, params);
9025
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
9026
0
    for (const auto &row : sqlRes) {
9027
0
        AuthorityFactory::CelestialBodyInfo info;
9028
0
        info.authName = row[0];
9029
0
        info.name = row[1];
9030
0
        res.emplace_back(info);
9031
0
    }
9032
0
    return res;
9033
0
}
9034
9035
// ---------------------------------------------------------------------------
9036
9037
/** \brief Gets the official name from a possibly alias name.
9038
 *
9039
 * @param aliasedName Alias name.
9040
 * @param tableName Table name/category. Can help in case of ambiguities.
9041
 * Or empty otherwise.
9042
 * @param source Source of the alias. Can help in case of ambiguities.
9043
 * Or empty otherwise.
9044
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
9045
 * the alt_name column of the alias_name table should be done with using
9046
 * metadata::Identifier::isEquivalentName() rather than strict string
9047
 * comparison;
9048
 * @param outTableName Table name in which the official name has been found.
9049
 * @param outAuthName Authority name of the official name that has been found.
9050
 * @param outCode Code of the official name that has been found.
9051
 * @return official name (or empty if not found).
9052
 * @throw FactoryException in case of error.
9053
 */
9054
std::string AuthorityFactory::getOfficialNameFromAlias(
9055
    const std::string &aliasedName, const std::string &tableName,
9056
    const std::string &source, bool tryEquivalentNameSpelling,
9057
    std::string &outTableName, std::string &outAuthName,
9058
3.17k
    std::string &outCode) const {
9059
9060
3.17k
    if (tryEquivalentNameSpelling) {
9061
0
        std::string sql(
9062
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
9063
0
        ListOfParams params;
9064
0
        if (!tableName.empty()) {
9065
0
            sql += " WHERE table_name = ?";
9066
0
            params.push_back(tableName);
9067
0
        }
9068
0
        if (!source.empty()) {
9069
0
            if (!tableName.empty()) {
9070
0
                sql += " AND ";
9071
0
            } else {
9072
0
                sql += " WHERE ";
9073
0
            }
9074
0
            sql += "source = ?";
9075
0
            params.push_back(source);
9076
0
        }
9077
0
        auto res = d->run(sql, params);
9078
0
        if (res.empty()) {
9079
0
            return std::string();
9080
0
        }
9081
0
        for (const auto &row : res) {
9082
0
            const auto &alt_name = row[3];
9083
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
9084
0
                                                       aliasedName.c_str())) {
9085
0
                outTableName = row[0];
9086
0
                outAuthName = row[1];
9087
0
                outCode = row[2];
9088
0
                sql = "SELECT name FROM \"";
9089
0
                sql += replaceAll(outTableName, "\"", "\"\"");
9090
0
                sql += "\" WHERE auth_name = ? AND code = ?";
9091
0
                res = d->run(sql, {outAuthName, outCode});
9092
0
                if (res.empty()) { // shouldn't happen normally
9093
0
                    return std::string();
9094
0
                }
9095
0
                return res.front()[0];
9096
0
            }
9097
0
        }
9098
0
        return std::string();
9099
3.17k
    } else {
9100
3.17k
        std::string sql(
9101
3.17k
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
9102
3.17k
            "alt_name = ?");
9103
3.17k
        ListOfParams params{aliasedName};
9104
3.17k
        if (!tableName.empty()) {
9105
3.17k
            sql += " AND table_name = ?";
9106
3.17k
            params.push_back(tableName);
9107
3.17k
        }
9108
3.17k
        if (!source.empty()) {
9109
3.17k
            if (source == "ESRI") {
9110
3.17k
                sql += " AND source IN ('ESRI', 'ESRI_OLD')";
9111
3.17k
            } else {
9112
0
                sql += " AND source = ?";
9113
0
                params.push_back(source);
9114
0
            }
9115
3.17k
        }
9116
3.17k
        auto res = d->run(sql, params);
9117
3.17k
        if (res.empty()) {
9118
2.11k
            return std::string();
9119
2.11k
        }
9120
9121
1.05k
        params.clear();
9122
1.05k
        sql.clear();
9123
1.05k
        bool first = true;
9124
1.05k
        for (const auto &row : res) {
9125
1.05k
            if (!first)
9126
0
                sql += " UNION ALL ";
9127
1.05k
            first = false;
9128
1.05k
            outTableName = row[0];
9129
1.05k
            outAuthName = row[1];
9130
1.05k
            outCode = row[2];
9131
1.05k
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
9132
1.05k
                   "FROM \"";
9133
1.05k
            sql += replaceAll(outTableName, "\"", "\"\"");
9134
1.05k
            sql += "\" WHERE auth_name = ? AND code = ?";
9135
1.05k
            params.emplace_back(outTableName);
9136
1.05k
            params.emplace_back(outAuthName);
9137
1.05k
            params.emplace_back(outCode);
9138
1.05k
        }
9139
1.05k
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
9140
1.05k
              ") x ORDER BY deprecated LIMIT 1";
9141
1.05k
        res = d->run(sql, params);
9142
1.05k
        if (res.empty()) { // shouldn't happen normally
9143
0
            return std::string();
9144
0
        }
9145
1.05k
        const auto &row = res.front();
9146
1.05k
        outTableName = row[1];
9147
1.05k
        outAuthName = row[2];
9148
1.05k
        outCode = row[3];
9149
1.05k
        return row[0];
9150
1.05k
    }
9151
3.17k
}
9152
9153
// ---------------------------------------------------------------------------
9154
9155
/** \brief Return a list of objects, identified by their name
9156
 *
9157
 * @param searchedName Searched name. Must be at least 2 character long.
9158
 * @param allowedObjectTypes List of object types into which to search. If
9159
 * empty, all object types will be searched.
9160
 * @param approximateMatch Whether approximate name identification is allowed.
9161
 * @param limitResultCount Maximum number of results to return.
9162
 * Or 0 for unlimited.
9163
 * @return list of matched objects.
9164
 * @throw FactoryException in case of error.
9165
 */
9166
std::list<common::IdentifiedObjectNNPtr>
9167
AuthorityFactory::createObjectsFromName(
9168
    const std::string &searchedName,
9169
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9170
56.8k
    size_t limitResultCount) const {
9171
56.8k
    std::list<common::IdentifiedObjectNNPtr> res;
9172
56.8k
    const auto resTmp(createObjectsFromNameEx(
9173
56.8k
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
9174
56.8k
    for (const auto &pair : resTmp) {
9175
25.1k
        res.emplace_back(pair.first);
9176
25.1k
    }
9177
56.8k
    return res;
9178
56.8k
}
9179
9180
// ---------------------------------------------------------------------------
9181
9182
//! @cond Doxygen_Suppress
9183
9184
/** \brief Return a list of objects, identifier by their name, with the name
9185
 * on which the match occurred.
9186
 *
9187
 * The name on which the match occurred might be different from the object name,
9188
 * if the match has been done on an alias name of that object.
9189
 *
9190
 * @param searchedName Searched name. Must be at least 2 character long.
9191
 * @param allowedObjectTypes List of object types into which to search. If
9192
 * empty, all object types will be searched.
9193
 * @param approximateMatch Whether approximate name identification is allowed.
9194
 * @param limitResultCount Maximum number of results to return.
9195
 * Or 0 for unlimited.
9196
 * @param useAliases Whether querying the alias_name table is allowed
9197
 * @return list of matched objects.
9198
 * @throw FactoryException in case of error.
9199
 */
9200
std::list<AuthorityFactory::PairObjectName>
9201
AuthorityFactory::createObjectsFromNameEx(
9202
    const std::string &searchedName,
9203
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9204
56.8k
    size_t limitResultCount, bool useAliases) const {
9205
56.8k
    std::string searchedNameWithoutDeprecated(searchedName);
9206
56.8k
    bool deprecated = false;
9207
56.8k
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
9208
0
        deprecated = true;
9209
0
        searchedNameWithoutDeprecated.resize(
9210
0
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
9211
0
    }
9212
9213
56.8k
    const std::string canonicalizedSearchedName(
9214
56.8k
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
9215
56.8k
    if (canonicalizedSearchedName.size() <= 1) {
9216
2.09k
        return {};
9217
2.09k
    }
9218
9219
54.8k
    std::string sql(
9220
54.8k
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
9221
54.8k
        "FROM (");
9222
9223
54.8k
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
9224
54.8k
                                             &searchedName]() {
9225
54.8k
        typedef std::pair<std::string, std::string> TableType;
9226
54.8k
        std::list<TableType> res;
9227
        // Hide ESRI D_ vertical datums
9228
54.8k
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
9229
54.8k
        if (allowedObjectTypes.empty()) {
9230
0
            for (const auto &tableName :
9231
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
9232
0
                  "vertical_datum", "engineering_datum", "geodetic_crs",
9233
0
                  "projected_crs", "vertical_crs", "compound_crs",
9234
0
                  "engineering_crs", "conversion", "helmert_transformation",
9235
0
                  "grid_transformation", "other_transformation",
9236
0
                  "concatenated_operation"}) {
9237
0
                if (!(startsWithDUnderscore &&
9238
0
                      strcmp(tableName, "vertical_datum") == 0)) {
9239
0
                    res.emplace_back(TableType(tableName, std::string()));
9240
0
                }
9241
0
            }
9242
54.8k
        } else {
9243
64.0k
            for (const auto type : allowedObjectTypes) {
9244
64.0k
                switch (type) {
9245
0
                case ObjectType::PRIME_MERIDIAN:
9246
0
                    res.emplace_back(
9247
0
                        TableType("prime_meridian", std::string()));
9248
0
                    break;
9249
3.95k
                case ObjectType::ELLIPSOID:
9250
3.95k
                    res.emplace_back(TableType("ellipsoid", std::string()));
9251
3.95k
                    break;
9252
3.06k
                case ObjectType::DATUM:
9253
3.06k
                    res.emplace_back(
9254
3.06k
                        TableType("geodetic_datum", std::string()));
9255
3.06k
                    if (!startsWithDUnderscore) {
9256
3.05k
                        res.emplace_back(
9257
3.05k
                            TableType("vertical_datum", std::string()));
9258
3.05k
                        res.emplace_back(
9259
3.05k
                            TableType("engineering_datum", std::string()));
9260
3.05k
                    }
9261
3.06k
                    break;
9262
16.8k
                case ObjectType::GEODETIC_REFERENCE_FRAME:
9263
16.8k
                    res.emplace_back(
9264
16.8k
                        TableType("geodetic_datum", std::string()));
9265
16.8k
                    break;
9266
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
9267
0
                    res.emplace_back(
9268
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
9269
0
                    break;
9270
100
                case ObjectType::VERTICAL_REFERENCE_FRAME:
9271
100
                    res.emplace_back(
9272
100
                        TableType("vertical_datum", std::string()));
9273
100
                    break;
9274
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
9275
0
                    res.emplace_back(
9276
0
                        TableType("vertical_datum", "frame_reference_epoch"));
9277
0
                    break;
9278
0
                case ObjectType::ENGINEERING_DATUM:
9279
0
                    res.emplace_back(
9280
0
                        TableType("engineering_datum", std::string()));
9281
0
                    break;
9282
4.71k
                case ObjectType::CRS:
9283
4.71k
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9284
4.71k
                    res.emplace_back(TableType("projected_crs", std::string()));
9285
4.71k
                    res.emplace_back(TableType("vertical_crs", std::string()));
9286
4.71k
                    res.emplace_back(TableType("compound_crs", std::string()));
9287
4.71k
                    res.emplace_back(
9288
4.71k
                        TableType("engineering_crs", std::string()));
9289
4.71k
                    break;
9290
0
                case ObjectType::GEODETIC_CRS:
9291
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9292
0
                    break;
9293
0
                case ObjectType::GEOCENTRIC_CRS:
9294
0
                    res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
9295
0
                    break;
9296
0
                case ObjectType::GEOGRAPHIC_CRS:
9297
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9298
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9299
0
                    break;
9300
9.42k
                case ObjectType::GEOGRAPHIC_2D_CRS:
9301
9.42k
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9302
9.42k
                    break;
9303
19.2k
                case ObjectType::GEOGRAPHIC_3D_CRS:
9304
19.2k
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9305
19.2k
                    break;
9306
214
                case ObjectType::PROJECTED_CRS:
9307
214
                    res.emplace_back(TableType("projected_crs", std::string()));
9308
214
                    break;
9309
200
                case ObjectType::VERTICAL_CRS:
9310
200
                    res.emplace_back(TableType("vertical_crs", std::string()));
9311
200
                    break;
9312
115
                case ObjectType::COMPOUND_CRS:
9313
115
                    res.emplace_back(TableType("compound_crs", std::string()));
9314
115
                    break;
9315
0
                case ObjectType::ENGINEERING_CRS:
9316
0
                    res.emplace_back(
9317
0
                        TableType("engineering_crs", std::string()));
9318
0
                    break;
9319
3.06k
                case ObjectType::COORDINATE_OPERATION:
9320
3.06k
                    res.emplace_back(TableType("conversion", std::string()));
9321
3.06k
                    res.emplace_back(
9322
3.06k
                        TableType("helmert_transformation", std::string()));
9323
3.06k
                    res.emplace_back(
9324
3.06k
                        TableType("grid_transformation", std::string()));
9325
3.06k
                    res.emplace_back(
9326
3.06k
                        TableType("other_transformation", std::string()));
9327
3.06k
                    res.emplace_back(
9328
3.06k
                        TableType("concatenated_operation", std::string()));
9329
3.06k
                    break;
9330
0
                case ObjectType::CONVERSION:
9331
0
                    res.emplace_back(TableType("conversion", std::string()));
9332
0
                    break;
9333
0
                case ObjectType::TRANSFORMATION:
9334
0
                    res.emplace_back(
9335
0
                        TableType("helmert_transformation", std::string()));
9336
0
                    res.emplace_back(
9337
0
                        TableType("grid_transformation", std::string()));
9338
0
                    res.emplace_back(
9339
0
                        TableType("other_transformation", std::string()));
9340
0
                    break;
9341
0
                case ObjectType::CONCATENATED_OPERATION:
9342
0
                    res.emplace_back(
9343
0
                        TableType("concatenated_operation", std::string()));
9344
0
                    break;
9345
3.07k
                case ObjectType::DATUM_ENSEMBLE:
9346
3.07k
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
9347
3.07k
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
9348
3.07k
                    break;
9349
64.0k
                }
9350
64.0k
            }
9351
54.8k
        }
9352
54.8k
        return res;
9353
54.8k
    };
9354
9355
54.8k
    bool datumEnsembleAllowed = false;
9356
54.8k
    if (allowedObjectTypes.empty()) {
9357
0
        datumEnsembleAllowed = true;
9358
54.8k
    } else {
9359
60.9k
        for (const auto type : allowedObjectTypes) {
9360
60.9k
            if (type == ObjectType::DATUM_ENSEMBLE) {
9361
3.07k
                datumEnsembleAllowed = true;
9362
3.07k
                break;
9363
3.07k
            }
9364
60.9k
        }
9365
54.8k
    }
9366
9367
54.8k
    const auto listTableNameType = getTableAndTypeConstraints();
9368
54.8k
    bool first = true;
9369
54.8k
    ListOfParams params;
9370
104k
    for (const auto &tableNameTypePair : listTableNameType) {
9371
104k
        if (!first) {
9372
49.4k
            sql += " UNION ";
9373
49.4k
        }
9374
104k
        first = false;
9375
104k
        sql += "SELECT '";
9376
104k
        sql += tableNameTypePair.first;
9377
104k
        sql += "' AS table_name, auth_name, code, name, deprecated, "
9378
104k
               "0 AS is_alias FROM ";
9379
104k
        sql += tableNameTypePair.first;
9380
104k
        sql += " WHERE 1 = 1 ";
9381
104k
        if (!tableNameTypePair.second.empty()) {
9382
34.8k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9383
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
9384
34.8k
            } else if (tableNameTypePair.second == "ensemble") {
9385
6.14k
                sql += "AND ensemble_accuracy IS NOT NULL ";
9386
28.7k
            } else {
9387
28.7k
                sql += "AND type = '";
9388
28.7k
                sql += tableNameTypePair.second;
9389
28.7k
                sql += "' ";
9390
28.7k
            }
9391
34.8k
        }
9392
104k
        if (deprecated) {
9393
0
            sql += "AND deprecated = 1 ";
9394
0
        }
9395
104k
        if (!approximateMatch) {
9396
84.2k
            sql += "AND name = ? COLLATE NOCASE ";
9397
84.2k
            params.push_back(searchedNameWithoutDeprecated);
9398
84.2k
        }
9399
104k
        if (d->hasAuthorityRestriction()) {
9400
20.1k
            sql += "AND auth_name = ? ";
9401
20.1k
            params.emplace_back(d->authority());
9402
20.1k
        }
9403
9404
104k
        if (useAliases) {
9405
104k
            sql += " UNION SELECT '";
9406
104k
            sql += tableNameTypePair.first;
9407
104k
            sql += "' AS table_name, "
9408
104k
                   "ov.auth_name AS auth_name, "
9409
104k
                   "ov.code AS code, a.alt_name AS name, "
9410
104k
                   "ov.deprecated AS deprecated, 1 as is_alias FROM ";
9411
104k
            sql += tableNameTypePair.first;
9412
104k
            sql += " ov "
9413
104k
                   "JOIN alias_name a ON "
9414
104k
                   "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
9415
104k
                   "a.source != 'EPSG_OLD' AND a.table_name = '";
9416
104k
            sql += tableNameTypePair.first;
9417
104k
            sql += "' ";
9418
104k
            if (!tableNameTypePair.second.empty()) {
9419
34.8k
                if (tableNameTypePair.second == "frame_reference_epoch") {
9420
0
                    sql += "AND ov.frame_reference_epoch IS NOT NULL ";
9421
34.8k
                } else if (tableNameTypePair.second == "ensemble") {
9422
6.14k
                    sql += "AND ov.ensemble_accuracy IS NOT NULL ";
9423
28.7k
                } else {
9424
28.7k
                    sql += "AND ov.type = '";
9425
28.7k
                    sql += tableNameTypePair.second;
9426
28.7k
                    sql += "' ";
9427
28.7k
                }
9428
34.8k
            }
9429
104k
            if (deprecated) {
9430
0
                sql += "AND ov.deprecated = 1 ";
9431
0
            }
9432
104k
            if (!approximateMatch) {
9433
84.2k
                sql += "AND a.alt_name = ? COLLATE NOCASE ";
9434
84.2k
                params.push_back(searchedNameWithoutDeprecated);
9435
84.2k
            }
9436
104k
            if (d->hasAuthorityRestriction()) {
9437
20.1k
                sql += "AND ov.auth_name = ? ";
9438
20.1k
                params.emplace_back(d->authority());
9439
20.1k
            }
9440
104k
        }
9441
104k
    }
9442
9443
54.8k
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
9444
54.8k
    if (limitResultCount > 0 &&
9445
34.6k
        limitResultCount <
9446
34.6k
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
9447
34.6k
        !approximateMatch) {
9448
31.0k
        sql += " LIMIT ";
9449
31.0k
        sql += toString(static_cast<int>(limitResultCount));
9450
31.0k
    }
9451
9452
54.8k
    std::list<PairObjectName> res;
9453
54.8k
    std::set<std::pair<std::string, std::string>> setIdentified;
9454
9455
    // Querying geodetic datum is a super hot path when importing from WKT1
9456
    // so cache results.
9457
54.8k
    if (allowedObjectTypes.size() == 1 &&
9458
51.7k
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
9459
16.8k
        approximateMatch && d->authority().empty()) {
9460
137
        auto &mapCanonicalizeGRFName =
9461
137
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
9462
137
        if (mapCanonicalizeGRFName.empty()) {
9463
83
            auto sqlRes = d->run(sql, params);
9464
206k
            for (const auto &row : sqlRes) {
9465
206k
                const auto &name = row[3];
9466
206k
                const auto &deprecatedStr = row[4];
9467
206k
                const auto canonicalizedName(
9468
206k
                    metadata::Identifier::canonicalizeName(name));
9469
206k
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
9470
206k
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
9471
198k
                    v.push_back(row);
9472
198k
                }
9473
206k
            }
9474
83
        }
9475
137
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
9476
137
        if (iter != mapCanonicalizeGRFName.end()) {
9477
14
            const auto &listOfRow = iter->second;
9478
14
            for (const auto &row : listOfRow) {
9479
14
                const auto &auth_name = row[1];
9480
14
                const auto &code = row[2];
9481
14
                auto key = std::pair<std::string, std::string>(auth_name, code);
9482
14
                if (setIdentified.find(key) != setIdentified.end()) {
9483
0
                    continue;
9484
0
                }
9485
14
                setIdentified.insert(std::move(key));
9486
14
                auto factory = d->createFactory(auth_name);
9487
14
                const auto &name = row[3];
9488
14
                res.emplace_back(
9489
14
                    PairObjectName(factory->createGeodeticDatum(code), name));
9490
14
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9491
14
                    break;
9492
14
                }
9493
14
            }
9494
123
        } else {
9495
172k
            for (const auto &pair : mapCanonicalizeGRFName) {
9496
172k
                const auto &listOfRow = pair.second;
9497
188k
                for (const auto &row : listOfRow) {
9498
188k
                    const auto &name = row[3];
9499
188k
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9500
188k
                                 std::string::npos;
9501
188k
                    if (!match) {
9502
188k
                        const auto &canonicalizedName(pair.first);
9503
188k
                        match = ci_find(canonicalizedName,
9504
188k
                                        canonicalizedSearchedName) !=
9505
188k
                                std::string::npos;
9506
188k
                    }
9507
188k
                    if (!match) {
9508
188k
                        continue;
9509
188k
                    }
9510
9511
58
                    const auto &auth_name = row[1];
9512
58
                    const auto &code = row[2];
9513
58
                    auto key =
9514
58
                        std::pair<std::string, std::string>(auth_name, code);
9515
58
                    if (setIdentified.find(key) != setIdentified.end()) {
9516
0
                        continue;
9517
0
                    }
9518
58
                    setIdentified.insert(std::move(key));
9519
58
                    auto factory = d->createFactory(auth_name);
9520
58
                    res.emplace_back(PairObjectName(
9521
58
                        factory->createGeodeticDatum(code), name));
9522
58
                    if (limitResultCount > 0 &&
9523
58
                        res.size() == limitResultCount) {
9524
58
                        break;
9525
58
                    }
9526
58
                }
9527
172k
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9528
58
                    break;
9529
58
                }
9530
172k
            }
9531
123
        }
9532
54.6k
    } else {
9533
54.6k
        auto sqlRes = d->run(sql, params);
9534
54.6k
        bool isFirst = true;
9535
54.6k
        bool firstIsDeprecated = false;
9536
54.6k
        size_t countExactMatch = 0;
9537
54.6k
        size_t countExactMatchOnAlias = 0;
9538
54.6k
        std::size_t hashCodeFirstMatch = 0;
9539
44.5M
        for (const auto &row : sqlRes) {
9540
44.5M
            const auto &name = row[3];
9541
44.5M
            if (approximateMatch) {
9542
44.5M
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9543
44.5M
                             std::string::npos;
9544
44.5M
                if (!match) {
9545
44.5M
                    const auto canonicalizedName(
9546
44.5M
                        metadata::Identifier::canonicalizeName(name));
9547
44.5M
                    match =
9548
44.5M
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
9549
44.5M
                        std::string::npos;
9550
44.5M
                }
9551
44.5M
                if (!match) {
9552
44.5M
                    continue;
9553
44.5M
                }
9554
44.5M
            }
9555
26.1k
            const auto &table_name = row[0];
9556
26.1k
            const auto &auth_name = row[1];
9557
26.1k
            const auto &code = row[2];
9558
26.1k
            auto key = std::pair<std::string, std::string>(auth_name, code);
9559
26.1k
            if (setIdentified.find(key) != setIdentified.end()) {
9560
881
                continue;
9561
881
            }
9562
25.2k
            setIdentified.insert(std::move(key));
9563
25.2k
            const auto &deprecatedStr = row[4];
9564
25.2k
            if (isFirst) {
9565
16.3k
                firstIsDeprecated = deprecatedStr == "1";
9566
16.3k
                isFirst = false;
9567
16.3k
            }
9568
25.2k
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
9569
88
                break;
9570
88
            }
9571
25.1k
            auto factory = d->createFactory(auth_name);
9572
25.1k
            auto getObject = [&factory, datumEnsembleAllowed](
9573
25.1k
                                 const std::string &l_table_name,
9574
25.1k
                                 const std::string &l_code)
9575
25.1k
                -> common::IdentifiedObjectNNPtr {
9576
25.1k
                if (l_table_name == "prime_meridian") {
9577
0
                    return factory->createPrimeMeridian(l_code);
9578
25.1k
                } else if (l_table_name == "ellipsoid") {
9579
221
                    return factory->createEllipsoid(l_code);
9580
24.9k
                } else if (l_table_name == "geodetic_datum") {
9581
379
                    if (datumEnsembleAllowed) {
9582
379
                        datum::GeodeticReferenceFramePtr datum;
9583
379
                        datum::DatumEnsemblePtr datumEnsemble;
9584
379
                        constexpr bool turnEnsembleAsDatum = false;
9585
379
                        factory->createGeodeticDatumOrEnsemble(
9586
379
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9587
379
                        if (datum) {
9588
379
                            return NN_NO_CHECK(datum);
9589
379
                        }
9590
379
                        assert(datumEnsemble);
9591
0
                        return NN_NO_CHECK(datumEnsemble);
9592
379
                    }
9593
0
                    return factory->createGeodeticDatum(l_code);
9594
24.5k
                } else if (l_table_name == "vertical_datum") {
9595
173
                    if (datumEnsembleAllowed) {
9596
173
                        datum::VerticalReferenceFramePtr datum;
9597
173
                        datum::DatumEnsemblePtr datumEnsemble;
9598
173
                        constexpr bool turnEnsembleAsDatum = false;
9599
173
                        factory->createVerticalDatumOrEnsemble(
9600
173
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9601
173
                        if (datum) {
9602
168
                            return NN_NO_CHECK(datum);
9603
168
                        }
9604
173
                        assert(datumEnsemble);
9605
5
                        return NN_NO_CHECK(datumEnsemble);
9606
173
                    }
9607
0
                    return factory->createVerticalDatum(l_code);
9608
24.3k
                } else if (l_table_name == "engineering_datum") {
9609
6
                    return factory->createEngineeringDatum(l_code);
9610
24.3k
                } else if (l_table_name == "geodetic_crs") {
9611
18.7k
                    return factory->createGeodeticCRS(l_code);
9612
18.7k
                } else if (l_table_name == "projected_crs") {
9613
3.09k
                    return factory->createProjectedCRS(l_code);
9614
3.09k
                } else if (l_table_name == "vertical_crs") {
9615
787
                    return factory->createVerticalCRS(l_code);
9616
1.72k
                } else if (l_table_name == "compound_crs") {
9617
162
                    return factory->createCompoundCRS(l_code);
9618
1.56k
                } else if (l_table_name == "engineering_crs") {
9619
8
                    return factory->createEngineeringCRS(l_code);
9620
1.55k
                } else if (l_table_name == "conversion") {
9621
490
                    return factory->createConversion(l_code);
9622
1.06k
                } else if (l_table_name == "grid_transformation" ||
9623
795
                           l_table_name == "helmert_transformation" ||
9624
262
                           l_table_name == "other_transformation" ||
9625
1.06k
                           l_table_name == "concatenated_operation") {
9626
1.06k
                    return factory->createCoordinateOperation(l_code, true);
9627
1.06k
                }
9628
0
                throw std::runtime_error("Unsupported table_name");
9629
25.1k
            };
9630
25.1k
            const auto obj = getObject(table_name, code);
9631
25.1k
            if (metadata::Identifier::isEquivalentName(
9632
25.1k
                    obj->nameStr().c_str(), searchedName.c_str(), false)) {
9633
14.2k
                countExactMatch++;
9634
14.2k
            } else if (metadata::Identifier::isEquivalentName(
9635
10.8k
                           name.c_str(), searchedName.c_str(), false)) {
9636
1.02k
                countExactMatchOnAlias++;
9637
1.02k
            }
9638
9639
25.1k
            const auto objPtr = obj.get();
9640
25.1k
            if (res.empty()) {
9641
16.3k
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9642
16.3k
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9643
6.71k
                hashCodeFirstMatch = 0;
9644
6.71k
            }
9645
9646
25.1k
            res.emplace_back(PairObjectName(obj, name));
9647
25.1k
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9648
946
                break;
9649
946
            }
9650
25.1k
        }
9651
9652
        // If we found several objects that are an exact match, and all objects
9653
        // have the same type, and we are not in approximate mode, only keep the
9654
        // objects with the exact name match.
9655
54.6k
        if ((countExactMatch + countExactMatchOnAlias) >= 1 &&
9656
15.1k
            hashCodeFirstMatch != 0 && !approximateMatch) {
9657
15.0k
            std::list<PairObjectName> resTmp;
9658
15.0k
            bool biggerDifferencesAllowed = (countExactMatch == 0);
9659
15.2k
            for (const auto &pair : res) {
9660
15.2k
                if (metadata::Identifier::isEquivalentName(
9661
15.2k
                        pair.first->nameStr().c_str(), searchedName.c_str(),
9662
15.2k
                        biggerDifferencesAllowed) ||
9663
982
                    (countExactMatch == 0 &&
9664
941
                     metadata::Identifier::isEquivalentName(
9665
941
                         pair.second.c_str(), searchedName.c_str(),
9666
15.1k
                         biggerDifferencesAllowed))) {
9667
15.1k
                    resTmp.emplace_back(pair);
9668
15.1k
                }
9669
15.2k
            }
9670
15.0k
            res = std::move(resTmp);
9671
15.0k
        }
9672
54.6k
    }
9673
9674
54.8k
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9675
16.2k
        const auto &aName = a.first->nameStr();
9676
16.2k
        const auto &bName = b.first->nameStr();
9677
9678
16.2k
        if (aName.size() < bName.size()) {
9679
562
            return true;
9680
562
        }
9681
15.7k
        if (aName.size() > bName.size()) {
9682
8.46k
            return false;
9683
8.46k
        }
9684
9685
7.26k
        const auto &aIds = a.first->identifiers();
9686
7.26k
        const auto &bIds = b.first->identifiers();
9687
7.26k
        if (aIds.size() < bIds.size()) {
9688
0
            return true;
9689
0
        }
9690
7.26k
        if (aIds.size() > bIds.size()) {
9691
0
            return false;
9692
0
        }
9693
7.26k
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9694
7.26k
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9695
7.26k
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9696
7.26k
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9697
7.26k
            if (codeSpaceComparison < 0) {
9698
680
                return true;
9699
680
            }
9700
6.58k
            if (codeSpaceComparison > 0) {
9701
827
                return false;
9702
827
            }
9703
5.75k
            const auto &aCode = aIds[idx]->code();
9704
5.75k
            const auto &bCode = bIds[idx]->code();
9705
5.75k
            const auto codeComparison = aCode.compare(bCode);
9706
5.75k
            if (codeComparison < 0) {
9707
1.66k
                return true;
9708
1.66k
            }
9709
4.08k
            if (codeComparison > 0) {
9710
4.08k
                return false;
9711
4.08k
            }
9712
4.08k
        }
9713
0
        return strcmp(typeid(a.first.get()).name(),
9714
0
                      typeid(b.first.get()).name()) < 0;
9715
7.26k
    };
9716
9717
54.8k
    res.sort(sortLambda);
9718
9719
54.8k
    return res;
9720
56.8k
}
9721
//! @endcond
9722
9723
// ---------------------------------------------------------------------------
9724
9725
/** \brief Return a list of area of use from their name
9726
 *
9727
 * @param name Searched name.
9728
 * @param approximateMatch Whether approximate name identification is allowed.
9729
 * @return list of (auth_name, code) of matched objects.
9730
 * @throw FactoryException in case of error.
9731
 */
9732
std::list<std::pair<std::string, std::string>>
9733
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9734
0
                                        bool approximateMatch) const {
9735
0
    std::string sql(
9736
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9737
0
    ListOfParams params;
9738
0
    if (d->hasAuthorityRestriction()) {
9739
0
        sql += " auth_name = ? AND ";
9740
0
        params.emplace_back(d->authority());
9741
0
    }
9742
0
    sql += "name LIKE ?";
9743
0
    if (!approximateMatch) {
9744
0
        params.push_back(name);
9745
0
    } else {
9746
0
        params.push_back('%' + name + '%');
9747
0
    }
9748
0
    auto sqlRes = d->run(sql, params);
9749
0
    std::list<std::pair<std::string, std::string>> res;
9750
0
    for (const auto &row : sqlRes) {
9751
0
        res.emplace_back(row[0], row[1]);
9752
0
    }
9753
0
    return res;
9754
0
}
9755
9756
// ---------------------------------------------------------------------------
9757
9758
//! @cond Doxygen_Suppress
9759
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9760
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9761
0
    std::string sql(
9762
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9763
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9764
0
        "((semi_minor_axis IS NOT NULL AND "
9765
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9766
0
        "((inv_flattening IS NOT NULL AND "
9767
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9768
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9769
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9770
0
                        ellipsoid->computedInverseFlattening()};
9771
0
    auto sqlRes = d->run(sql, params);
9772
0
    std::list<datum::EllipsoidNNPtr> res;
9773
0
    for (const auto &row : sqlRes) {
9774
0
        const auto &auth_name = row[0];
9775
0
        const auto &code = row[1];
9776
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9777
0
    }
9778
0
    return res;
9779
0
}
9780
//! @endcond
9781
9782
// ---------------------------------------------------------------------------
9783
9784
//! @cond Doxygen_Suppress
9785
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9786
    const std::string &datum_auth_name, const std::string &datum_code,
9787
28.4k
    const std::string &geodetic_crs_type) const {
9788
28.4k
    std::string sql(
9789
28.4k
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9790
28.4k
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9791
28.4k
    ListOfParams params{datum_auth_name, datum_code};
9792
28.4k
    if (d->hasAuthorityRestriction()) {
9793
3.17k
        sql += " AND auth_name = ?";
9794
3.17k
        params.emplace_back(d->authority());
9795
3.17k
    }
9796
28.4k
    if (!geodetic_crs_type.empty()) {
9797
0
        sql += " AND type = ?";
9798
0
        params.emplace_back(geodetic_crs_type);
9799
0
    }
9800
28.4k
    sql += " ORDER BY auth_name, code";
9801
28.4k
    auto sqlRes = d->run(sql, params);
9802
28.4k
    std::list<crs::GeodeticCRSNNPtr> res;
9803
160k
    for (const auto &row : sqlRes) {
9804
160k
        const auto &auth_name = row[0];
9805
160k
        const auto &code = row[1];
9806
160k
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9807
160k
    }
9808
28.4k
    return res;
9809
28.4k
}
9810
//! @endcond
9811
9812
// ---------------------------------------------------------------------------
9813
9814
//! @cond Doxygen_Suppress
9815
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9816
    const datum::GeodeticReferenceFrameNNPtr &datum,
9817
    const std::string &preferredAuthName,
9818
45.1k
    const std::string &geodetic_crs_type) const {
9819
45.1k
    std::list<crs::GeodeticCRSNNPtr> candidates;
9820
45.1k
    const auto &ids = datum->identifiers();
9821
45.1k
    const auto &datumName = datum->nameStr();
9822
45.1k
    if (!ids.empty()) {
9823
28.4k
        for (const auto &id : ids) {
9824
28.4k
            const auto &authName = *(id->codeSpace());
9825
28.4k
            const auto &code = id->code();
9826
28.4k
            if (!authName.empty()) {
9827
28.4k
                const auto tmpFactory =
9828
28.4k
                    (preferredAuthName == authName)
9829
28.4k
                        ? create(databaseContext(), authName)
9830
28.4k
                        : NN_NO_CHECK(d->getSharedFromThis());
9831
28.4k
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9832
28.4k
                    authName, code, geodetic_crs_type);
9833
160k
                for (const auto &candidate : l_candidates) {
9834
160k
                    candidates.emplace_back(candidate);
9835
160k
                }
9836
28.4k
            }
9837
28.4k
        }
9838
28.4k
    } else if (datumName != "unknown" && datumName != "unnamed") {
9839
16.7k
        auto matches = createObjectsFromName(
9840
16.7k
            datumName,
9841
16.7k
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
9842
16.7k
            2);
9843
16.7k
        if (matches.size() == 1) {
9844
0
            const auto &match = matches.front();
9845
0
            if (datum->_isEquivalentTo(match.get(),
9846
0
                                       util::IComparable::Criterion::EQUIVALENT,
9847
0
                                       databaseContext().as_nullable()) &&
9848
0
                !match->identifiers().empty()) {
9849
0
                return createGeodeticCRSFromDatum(
9850
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
9851
0
                        match),
9852
0
                    preferredAuthName, geodetic_crs_type);
9853
0
            }
9854
0
        }
9855
16.7k
    }
9856
45.1k
    return candidates;
9857
45.1k
}
9858
//! @endcond
9859
9860
// ---------------------------------------------------------------------------
9861
9862
//! @cond Doxygen_Suppress
9863
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
9864
111
    const std::string &datum_auth_name, const std::string &datum_code) const {
9865
111
    std::string sql(
9866
111
        "SELECT auth_name, code FROM vertical_crs WHERE "
9867
111
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9868
111
    ListOfParams params{datum_auth_name, datum_code};
9869
111
    if (d->hasAuthorityRestriction()) {
9870
0
        sql += " AND auth_name = ?";
9871
0
        params.emplace_back(d->authority());
9872
0
    }
9873
111
    auto sqlRes = d->run(sql, params);
9874
111
    std::list<crs::VerticalCRSNNPtr> res;
9875
167
    for (const auto &row : sqlRes) {
9876
167
        const auto &auth_name = row[0];
9877
167
        const auto &code = row[1];
9878
167
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
9879
167
    }
9880
111
    return res;
9881
111
}
9882
//! @endcond
9883
9884
// ---------------------------------------------------------------------------
9885
9886
//! @cond Doxygen_Suppress
9887
std::list<crs::GeodeticCRSNNPtr>
9888
AuthorityFactory::createGeodeticCRSFromEllipsoid(
9889
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
9890
0
    const std::string &geodetic_crs_type) const {
9891
0
    std::string sql(
9892
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
9893
0
        "JOIN geodetic_datum ON "
9894
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
9895
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
9896
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
9897
0
        "geodetic_datum.ellipsoid_code = ? AND "
9898
0
        "geodetic_datum.deprecated = 0 AND "
9899
0
        "geodetic_crs.deprecated = 0");
9900
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
9901
0
    if (d->hasAuthorityRestriction()) {
9902
0
        sql += " AND geodetic_crs.auth_name = ?";
9903
0
        params.emplace_back(d->authority());
9904
0
    }
9905
0
    if (!geodetic_crs_type.empty()) {
9906
0
        sql += " AND geodetic_crs.type = ?";
9907
0
        params.emplace_back(geodetic_crs_type);
9908
0
    }
9909
0
    auto sqlRes = d->run(sql, params);
9910
0
    std::list<crs::GeodeticCRSNNPtr> res;
9911
0
    for (const auto &row : sqlRes) {
9912
0
        const auto &auth_name = row[0];
9913
0
        const auto &code = row[1];
9914
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9915
0
    }
9916
0
    return res;
9917
0
}
9918
//! @endcond
9919
9920
// ---------------------------------------------------------------------------
9921
9922
//! @cond Doxygen_Suppress
9923
static std::string buildSqlLookForAuthNameCode(
9924
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
9925
0
    const char *prefixField) {
9926
0
    std::string sql("(");
9927
9928
0
    std::set<std::string> authorities;
9929
0
    for (const auto &crs : list) {
9930
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9931
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9932
0
                                   : crs.first->identifiers();
9933
0
        if (!ids.empty()) {
9934
0
            authorities.insert(*(ids[0]->codeSpace()));
9935
0
        }
9936
0
    }
9937
0
    bool firstAuth = true;
9938
0
    for (const auto &auth_name : authorities) {
9939
0
        if (!firstAuth) {
9940
0
            sql += " OR ";
9941
0
        }
9942
0
        firstAuth = false;
9943
0
        sql += "( ";
9944
0
        sql += prefixField;
9945
0
        sql += "auth_name = ? AND ";
9946
0
        sql += prefixField;
9947
0
        sql += "code IN (";
9948
0
        params.emplace_back(auth_name);
9949
0
        bool firstGeodCRSForAuth = true;
9950
0
        for (const auto &crs : list) {
9951
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9952
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9953
0
                                       : crs.first->identifiers();
9954
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
9955
0
                if (!firstGeodCRSForAuth) {
9956
0
                    sql += ',';
9957
0
                }
9958
0
                firstGeodCRSForAuth = false;
9959
0
                sql += '?';
9960
0
                params.emplace_back(ids[0]->code());
9961
0
            }
9962
0
        }
9963
0
        sql += "))";
9964
0
    }
9965
0
    sql += ')';
9966
0
    return sql;
9967
0
}
9968
//! @endcond
9969
9970
// ---------------------------------------------------------------------------
9971
9972
//! @cond Doxygen_Suppress
9973
std::list<crs::ProjectedCRSNNPtr>
9974
AuthorityFactory::createProjectedCRSFromExisting(
9975
0
    const crs::ProjectedCRSNNPtr &crs) const {
9976
0
    std::list<crs::ProjectedCRSNNPtr> res;
9977
9978
0
    const auto &conv = crs->derivingConversionRef();
9979
0
    const auto &method = conv->method();
9980
0
    const auto methodEPSGCode = method->getEPSGCode();
9981
0
    if (methodEPSGCode == 0) {
9982
0
        return res;
9983
0
    }
9984
9985
0
    auto lockedThisFactory(d->getSharedFromThis());
9986
0
    assert(lockedThisFactory);
9987
0
    const auto &baseCRS(crs->baseCRS());
9988
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
9989
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
9990
0
    if (geogCRS) {
9991
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
9992
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
9993
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
9994
0
            const auto &unit =
9995
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
9996
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
9997
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
9998
0
                                        geogCRS->nameStr()),
9999
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
10000
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
10001
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
10002
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
10003
0
            auto otherCandidatesGeodCRS =
10004
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
10005
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
10006
0
                                     otherCandidatesGeodCRS.begin(),
10007
0
                                     otherCandidatesGeodCRS.end());
10008
0
        }
10009
0
    }
10010
10011
0
    std::string sql(
10012
0
        "SELECT projected_crs.auth_name, projected_crs.code, "
10013
0
        "projected_crs.name FROM projected_crs "
10014
0
        "JOIN conversion_table conv ON "
10015
0
        "projected_crs.conversion_auth_name = conv.auth_name AND "
10016
0
        "projected_crs.conversion_code = conv.code "
10017
0
        "JOIN geodetic_crs gcrs ON "
10018
0
        "gcrs.auth_name = projected_crs.geodetic_crs_auth_name AND "
10019
0
        "gcrs.code = projected_crs.geodetic_crs_code "
10020
0
        "JOIN geodetic_datum datum ON "
10021
0
        "datum.auth_name = gcrs.datum_auth_name AND "
10022
0
        "datum.code = gcrs.datum_code "
10023
0
        "JOIN ellipsoid ellps ON "
10024
0
        "ellps.auth_name = datum.ellipsoid_auth_name AND "
10025
0
        "ellps.code = datum.ellipsoid_code "
10026
0
        "WHERE "
10027
0
        "abs(ellps.semi_major_axis - ?) <= 1e-4 * ellps.semi_major_axis AND "
10028
0
        "projected_crs.deprecated = 0 AND ");
10029
0
    ListOfParams params;
10030
0
    params.emplace_back(
10031
0
        toString(crs->baseCRS()->ellipsoid()->semiMajorAxis().value()));
10032
0
    if (!candidatesGeodCRS.empty()) {
10033
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10034
0
                                           "projected_crs.geodetic_crs_");
10035
0
        sql += " AND ";
10036
0
    }
10037
0
    sql += "conv.method_auth_name = 'EPSG' AND "
10038
0
           "conv.method_code = ?";
10039
0
    params.emplace_back(toString(methodEPSGCode));
10040
0
    if (d->hasAuthorityRestriction()) {
10041
0
        sql += " AND projected_crs.auth_name = ?";
10042
0
        params.emplace_back(d->authority());
10043
0
    }
10044
10045
0
    int iParam = 0;
10046
0
    bool hasLat1stStd = false;
10047
0
    double lat1stStd = 0;
10048
0
    int iParamLat1stStd = 0;
10049
0
    bool hasLat2ndStd = false;
10050
0
    double lat2ndStd = 0;
10051
0
    int iParamLat2ndStd = 0;
10052
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
10053
0
        iParam++;
10054
0
        auto opParamvalue =
10055
0
            dynamic_cast<const operation::OperationParameterValue *>(
10056
0
                genOpParamvalue.get());
10057
0
        if (!opParamvalue) {
10058
0
            break;
10059
0
        }
10060
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
10061
0
        const auto &parameterValue = opParamvalue->parameterValue();
10062
0
        if (!(paramEPSGCode > 0 &&
10063
0
              parameterValue->type() ==
10064
0
                  operation::ParameterValue::Type::MEASURE)) {
10065
0
            break;
10066
0
        }
10067
0
        const auto &measure = parameterValue->value();
10068
0
        const auto &unit = measure.unit();
10069
0
        if (unit == common::UnitOfMeasure::DEGREE &&
10070
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
10071
0
            if (methodEPSGCode ==
10072
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
10073
                // Special case for standard parallels of LCC_2SP. See below
10074
0
                if (paramEPSGCode ==
10075
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
10076
0
                    hasLat1stStd = true;
10077
0
                    lat1stStd = measure.value();
10078
0
                    iParamLat1stStd = iParam;
10079
0
                    continue;
10080
0
                } else if (paramEPSGCode ==
10081
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
10082
0
                    hasLat2ndStd = true;
10083
0
                    lat2ndStd = measure.value();
10084
0
                    iParamLat2ndStd = iParam;
10085
0
                    continue;
10086
0
                }
10087
0
            }
10088
0
            const auto iParamAsStr(toString(iParam));
10089
0
            sql += " AND conv.param";
10090
0
            sql += iParamAsStr;
10091
0
            sql += "_code = ? AND conv.param";
10092
0
            sql += iParamAsStr;
10093
0
            sql += "_auth_name = 'EPSG' AND conv.param";
10094
0
            sql += iParamAsStr;
10095
0
            sql += "_value BETWEEN ? AND ?";
10096
            // As angles might be expressed with the odd unit EPSG:9110
10097
            // "sexagesimal DMS", we have to provide a broad range
10098
0
            params.emplace_back(toString(paramEPSGCode));
10099
0
            params.emplace_back(measure.value() - 1);
10100
0
            params.emplace_back(measure.value() + 1);
10101
0
        }
10102
0
    }
10103
10104
    // Special case for standard parallels of LCC_2SP: they can be switched
10105
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
10106
0
        hasLat1stStd && hasLat2ndStd) {
10107
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
10108
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
10109
0
        sql += " AND conv.param";
10110
0
        sql += iParam1AsStr;
10111
0
        sql += "_code = ? AND conv.param";
10112
0
        sql += iParam1AsStr;
10113
0
        sql += "_auth_name = 'EPSG' AND conv.param";
10114
0
        sql += iParam2AsStr;
10115
0
        sql += "_code = ? AND conv.param";
10116
0
        sql += iParam2AsStr;
10117
0
        sql += "_auth_name = 'EPSG' AND ((";
10118
0
        params.emplace_back(
10119
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
10120
0
        params.emplace_back(
10121
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
10122
0
        double val1 = lat1stStd;
10123
0
        double val2 = lat2ndStd;
10124
0
        for (int i = 0; i < 2; i++) {
10125
0
            if (i == 1) {
10126
0
                sql += ") OR (";
10127
0
                std::swap(val1, val2);
10128
0
            }
10129
0
            sql += "conv.param";
10130
0
            sql += iParam1AsStr;
10131
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
10132
0
            sql += iParam2AsStr;
10133
0
            sql += "_value BETWEEN ? AND ?";
10134
0
            params.emplace_back(val1 - 1);
10135
0
            params.emplace_back(val1 + 1);
10136
0
            params.emplace_back(val2 - 1);
10137
0
            params.emplace_back(val2 + 1);
10138
0
        }
10139
0
        sql += "))";
10140
0
    }
10141
0
    auto sqlRes = d->run(sql, params);
10142
10143
0
    for (const auto &row : sqlRes) {
10144
0
        const auto &name = row[2];
10145
0
        if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(),
10146
0
                                                   name.c_str())) {
10147
0
            const auto &auth_name = row[0];
10148
0
            const auto &code = row[1];
10149
0
            res.emplace_back(
10150
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10151
0
        }
10152
0
    }
10153
0
    if (!res.empty()) {
10154
0
        return res;
10155
0
    }
10156
10157
0
    params.clear();
10158
10159
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
10160
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
10161
0
    if (!candidatesGeodCRS.empty()) {
10162
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10163
0
                                           "geodetic_crs_");
10164
0
        sql += " AND ";
10165
0
    }
10166
10167
0
    const auto escapeLikeStr = [](const std::string &str) {
10168
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
10169
0
                          "%", "\\%");
10170
0
    };
10171
10172
0
    const auto ellpsSemiMajorStr =
10173
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
10174
10175
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
10176
10177
    // WKT2 definition
10178
0
    {
10179
0
        std::string patternVal("%");
10180
10181
0
        patternVal += ',';
10182
0
        patternVal += ellpsSemiMajorStr;
10183
0
        patternVal += '%';
10184
10185
0
        patternVal += escapeLikeStr(method->nameStr());
10186
0
        patternVal += '%';
10187
10188
0
        params.emplace_back(patternVal);
10189
0
    }
10190
10191
0
    const auto *mapping = getMapping(method.get());
10192
0
    if (mapping && mapping->proj_name_main) {
10193
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
10194
10195
0
        std::string patternVal("%");
10196
0
        patternVal += "proj=";
10197
0
        patternVal += mapping->proj_name_main;
10198
0
        patternVal += '%';
10199
0
        params.emplace_back(patternVal);
10200
10201
        // could be a= or R=
10202
0
        patternVal = "%=";
10203
0
        patternVal += ellpsSemiMajorStr;
10204
0
        patternVal += '%';
10205
0
        params.emplace_back(patternVal);
10206
10207
0
        std::string projEllpsName;
10208
0
        std::string ellpsName;
10209
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
10210
0
                                                            ellpsName)) {
10211
0
            sql += " OR text_definition LIKE ?";
10212
            // Could be ellps= or datum=
10213
0
            patternVal = "%=";
10214
0
            patternVal += projEllpsName;
10215
0
            patternVal += '%';
10216
0
            params.emplace_back(patternVal);
10217
0
        }
10218
10219
0
        sql += "))";
10220
0
    }
10221
10222
    // WKT1_GDAL definition
10223
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
10224
0
    if (wkt1GDALMethodName) {
10225
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10226
0
        std::string patternVal("%");
10227
10228
0
        patternVal += ',';
10229
0
        patternVal += ellpsSemiMajorStr;
10230
0
        patternVal += '%';
10231
10232
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
10233
0
        patternVal += '%';
10234
10235
0
        params.emplace_back(patternVal);
10236
0
    }
10237
10238
    // WKT1_ESRI definition
10239
0
    const char *esriMethodName = conv->getESRIMethodName();
10240
0
    if (esriMethodName) {
10241
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10242
0
        std::string patternVal("%");
10243
10244
0
        patternVal += ',';
10245
0
        patternVal += ellpsSemiMajorStr;
10246
0
        patternVal += '%';
10247
10248
0
        patternVal += escapeLikeStr(esriMethodName);
10249
0
        patternVal += '%';
10250
10251
0
        auto fe =
10252
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
10253
0
        if (*fe == Measure()) {
10254
0
            fe = &conv->parameterValueMeasure(
10255
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
10256
0
        }
10257
0
        if (!(*fe == Measure())) {
10258
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
10259
0
            patternVal +=
10260
0
                toString(fe->convertToUnit(
10261
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
10262
0
                         10);
10263
0
            patternVal += '%';
10264
0
        }
10265
10266
0
        auto lat = &conv->parameterValueMeasure(
10267
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
10268
0
        if (*lat == Measure()) {
10269
0
            lat = &conv->parameterValueMeasure(
10270
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
10271
0
        }
10272
0
        if (!(*lat == Measure())) {
10273
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
10274
0
            const auto &angularUnit =
10275
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
10276
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
10277
0
                    : UnitOfMeasure::DEGREE;
10278
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
10279
0
            patternVal += '%';
10280
0
        }
10281
10282
0
        params.emplace_back(patternVal);
10283
0
    }
10284
0
    sql += ")";
10285
0
    if (d->hasAuthorityRestriction()) {
10286
0
        sql += " AND auth_name = ?";
10287
0
        params.emplace_back(d->authority());
10288
0
    }
10289
10290
0
    auto sqlRes2 = d->run(sql, params);
10291
10292
0
    if (sqlRes.size() <= 200) {
10293
0
        for (const auto &row : sqlRes) {
10294
0
            const auto &auth_name = row[0];
10295
0
            const auto &code = row[1];
10296
0
            res.emplace_back(
10297
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10298
0
        }
10299
0
    }
10300
0
    if (sqlRes2.size() <= 200) {
10301
0
        for (const auto &row : sqlRes2) {
10302
0
            const auto &auth_name = row[0];
10303
0
            const auto &code = row[1];
10304
0
            res.emplace_back(
10305
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10306
0
        }
10307
0
    }
10308
10309
0
    return res;
10310
0
}
10311
10312
// ---------------------------------------------------------------------------
10313
10314
std::list<crs::CompoundCRSNNPtr>
10315
AuthorityFactory::createCompoundCRSFromExisting(
10316
0
    const crs::CompoundCRSNNPtr &crs) const {
10317
0
    std::list<crs::CompoundCRSNNPtr> res;
10318
10319
0
    auto lockedThisFactory(d->getSharedFromThis());
10320
0
    assert(lockedThisFactory);
10321
10322
0
    const auto &components = crs->componentReferenceSystems();
10323
0
    if (components.size() != 2) {
10324
0
        return res;
10325
0
    }
10326
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
10327
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
10328
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
10329
0
        return res;
10330
0
    }
10331
10332
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
10333
0
                    "deprecated = 0 AND ");
10334
0
    ListOfParams params;
10335
0
    bool addAnd = false;
10336
0
    if (!candidatesHorizCRS.empty()) {
10337
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
10338
0
                                           "horiz_crs_");
10339
0
        addAnd = true;
10340
0
    }
10341
0
    if (!candidatesVertCRS.empty()) {
10342
0
        if (addAnd) {
10343
0
            sql += " AND ";
10344
0
        }
10345
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
10346
0
                                           "vertical_crs_");
10347
0
        addAnd = true;
10348
0
    }
10349
0
    if (d->hasAuthorityRestriction()) {
10350
0
        if (addAnd) {
10351
0
            sql += " AND ";
10352
0
        }
10353
0
        sql += "auth_name = ?";
10354
0
        params.emplace_back(d->authority());
10355
0
    }
10356
10357
0
    auto sqlRes = d->run(sql, params);
10358
0
    for (const auto &row : sqlRes) {
10359
0
        const auto &auth_name = row[0];
10360
0
        const auto &code = row[1];
10361
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
10362
0
    }
10363
0
    return res;
10364
0
}
10365
10366
// ---------------------------------------------------------------------------
10367
10368
std::vector<operation::CoordinateOperationNNPtr>
10369
AuthorityFactory::getTransformationsForGeoid(
10370
156
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
10371
156
    std::vector<operation::CoordinateOperationNNPtr> res;
10372
10373
156
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
10374
156
                          "geoid_model WHERE name = ?");
10375
156
    auto sqlRes = d->run(sql, {geoidName});
10376
156
    for (const auto &row : sqlRes) {
10377
0
        const auto &auth_name = row[0];
10378
0
        const auto &code = row[1];
10379
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
10380
0
            code, usePROJAlternativeGridNames));
10381
0
    }
10382
10383
156
    return res;
10384
156
}
10385
10386
// ---------------------------------------------------------------------------
10387
10388
std::vector<operation::PointMotionOperationNNPtr>
10389
AuthorityFactory::getPointMotionOperationsFor(
10390
7
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
10391
7
    std::vector<operation::PointMotionOperationNNPtr> res;
10392
7
    const auto crsList =
10393
7
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
10394
7
                                   /* preferredAuthName = */ std::string(),
10395
7
                                   /* geodetic_crs_type = */ std::string());
10396
7
    if (crsList.empty())
10397
0
        return res;
10398
7
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
10399
7
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
10400
7
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
10401
7
                    "(");
10402
7
    bool addOr = false;
10403
7
    ListOfParams params;
10404
24
    for (const auto &candidateCrs : crsList) {
10405
24
        if (addOr)
10406
17
            sql += " OR ";
10407
24
        addOr = true;
10408
24
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
10409
24
        const auto &ids = candidateCrs->identifiers();
10410
24
        params.emplace_back(*(ids[0]->codeSpace()));
10411
24
        params.emplace_back(ids[0]->code());
10412
24
    }
10413
7
    sql += ")";
10414
7
    if (d->hasAuthorityRestriction()) {
10415
0
        sql += " AND auth_name = ?";
10416
0
        params.emplace_back(d->authority());
10417
0
    }
10418
10419
7
    auto sqlRes = d->run(sql, params);
10420
7
    for (const auto &row : sqlRes) {
10421
0
        const auto &auth_name = row[0];
10422
0
        const auto &code = row[1];
10423
0
        auto pmo =
10424
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
10425
0
                d->createFactory(auth_name)->createCoordinateOperation(
10426
0
                    code, usePROJAlternativeGridNames));
10427
0
        if (pmo) {
10428
0
            res.emplace_back(NN_NO_CHECK(pmo));
10429
0
        }
10430
0
    }
10431
7
    return res;
10432
7
}
10433
10434
//! @endcond
10435
10436
// ---------------------------------------------------------------------------
10437
10438
//! @cond Doxygen_Suppress
10439
1.47k
FactoryException::FactoryException(const char *message) : Exception(message) {}
10440
10441
// ---------------------------------------------------------------------------
10442
10443
FactoryException::FactoryException(const std::string &message)
10444
532
    : Exception(message) {}
10445
10446
// ---------------------------------------------------------------------------
10447
10448
2.00k
FactoryException::~FactoryException() = default;
10449
10450
// ---------------------------------------------------------------------------
10451
10452
0
FactoryException::FactoryException(const FactoryException &) = default;
10453
//! @endcond
10454
10455
// ---------------------------------------------------------------------------
10456
10457
//! @cond Doxygen_Suppress
10458
10459
struct NoSuchAuthorityCodeException::Private {
10460
    std::string authority_;
10461
    std::string code_;
10462
10463
    Private(const std::string &authority, const std::string &code)
10464
532
        : authority_(authority), code_(code) {}
10465
};
10466
10467
// ---------------------------------------------------------------------------
10468
10469
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10470
    const std::string &message, const std::string &authority,
10471
    const std::string &code)
10472
532
    : FactoryException(message), d(std::make_unique<Private>(authority, code)) {
10473
532
}
10474
10475
// ---------------------------------------------------------------------------
10476
10477
532
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
10478
10479
// ---------------------------------------------------------------------------
10480
10481
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10482
    const NoSuchAuthorityCodeException &other)
10483
0
    : FactoryException(other), d(std::make_unique<Private>(*(other.d))) {}
10484
//! @endcond
10485
10486
// ---------------------------------------------------------------------------
10487
10488
/** \brief Returns authority name. */
10489
101
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
10490
101
    return d->authority_;
10491
101
}
10492
10493
// ---------------------------------------------------------------------------
10494
10495
/** \brief Returns authority code. */
10496
101
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
10497
101
    return d->code_;
10498
101
}
10499
10500
// ---------------------------------------------------------------------------
10501
10502
} // namespace io
10503
NS_PROJ_END
10504
10505
// ---------------------------------------------------------------------------
10506
10507
12.4k
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }