Coverage Report

Created: 2024-02-25 06:14

/src/PROJ/src/iso19111/factory.cpp
Line
Count
Source (jump to first uncovered line)
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
// Custom SQLite VFS as our database is not supposed to be modified in
80
// parallel. This is slightly faster
81
#define ENABLE_CUSTOM_LOCKLESS_VFS
82
83
#if defined(_WIN32) && defined(PROJ_HAS_PTHREADS)
84
#undef PROJ_HAS_PTHREADS
85
#endif
86
87
/* SQLite3 might use seak()+read() or pread[64]() to read data */
88
/* The later allows the same SQLite handle to be safely used in forked */
89
/* children of a parent process, while the former doesn't. */
90
/* So we use pthread_atfork() to set a flag in forked children, to ask them */
91
/* to close and reopen their database handle. */
92
#if defined(PROJ_HAS_PTHREADS) && !defined(SQLITE_USE_PREAD)
93
#include <pthread.h>
94
#define REOPEN_SQLITE_DB_AFTER_FORK
95
#endif
96
97
using namespace NS_PROJ::internal;
98
using namespace NS_PROJ::common;
99
100
NS_PROJ_START
101
namespace io {
102
103
//! @cond Doxygen_Suppress
104
105
// CRS subtypes
106
0
#define GEOG_2D "geographic 2D"
107
0
#define GEOG_3D "geographic 3D"
108
0
#define GEOCENTRIC "geocentric"
109
0
#define OTHER "other"
110
0
#define PROJECTED "projected"
111
0
#define VERTICAL "vertical"
112
0
#define COMPOUND "compound"
113
114
0
#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
115
0
#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
116
#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
117
118
// Coordinate system types
119
constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE;
120
constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE;
121
constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE;
122
constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE;
123
constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE;
124
125
// See data/sql/metadata.sql for the semantics of those constants
126
constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
127
// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
128
// must be incremented.
129
constexpr int DATABASE_LAYOUT_VERSION_MINOR = 3;
130
131
constexpr size_t N_MAX_PARAMS = 7;
132
133
// ---------------------------------------------------------------------------
134
135
struct SQLValues {
136
    enum class Type { STRING, INT, DOUBLE };
137
138
    // cppcheck-suppress noExplicitConstructor
139
0
    SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
140
141
    // cppcheck-suppress noExplicitConstructor
142
0
    SQLValues(int value) : type_(Type::INT), int_(value) {}
143
144
    // cppcheck-suppress noExplicitConstructor
145
0
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
146
147
0
    const Type &type() const { return type_; }
148
149
    // cppcheck-suppress functionStatic
150
0
    const std::string &stringValue() const { return str_; }
151
152
    // cppcheck-suppress functionStatic
153
0
    int intValue() const { return int_; }
154
155
    // cppcheck-suppress functionStatic
156
0
    double doubleValue() const { return double_; }
157
158
  private:
159
    Type type_;
160
    std::string str_{};
161
    int int_ = 0;
162
    double double_ = 0.0;
163
};
164
165
// ---------------------------------------------------------------------------
166
167
using SQLRow = std::vector<std::string>;
168
using SQLResultSet = std::list<SQLRow>;
169
using ListOfParams = std::list<SQLValues>;
170
171
// ---------------------------------------------------------------------------
172
173
0
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
174
0
    switch (sqlite3_value_type(val)) {
175
0
    case SQLITE_FLOAT:
176
0
        gotVal = true;
177
0
        return sqlite3_value_double(val);
178
179
0
    case SQLITE_INTEGER:
180
0
        gotVal = true;
181
0
        return static_cast<double>(sqlite3_value_int64(val));
182
183
0
    default:
184
0
        gotVal = false;
185
0
        return 0.0;
186
0
    }
187
0
}
188
189
// ---------------------------------------------------------------------------
190
191
static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
192
                                              int /* argc */,
193
0
                                              sqlite3_value **argv) {
194
0
    bool b0, b1, b2, b3;
195
0
    double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
196
0
    double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
197
0
    double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
198
0
    double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
199
0
    if (!b0 || !b1 || !b2 || !b3) {
200
0
        sqlite3_result_null(pContext);
201
0
        return;
202
0
    }
203
    // Deal with area crossing antimeridian
204
0
    if (east_lon < west_lon) {
205
0
        east_lon += 360.0;
206
0
    }
207
    // Integrate cos(lat) between south_lat and north_lat
208
0
    double pseudo_area = (east_lon - west_lon) *
209
0
                         (std::sin(common::Angle(north_lat).getSIValue()) -
210
0
                          std::sin(common::Angle(south_lat).getSIValue()));
211
0
    sqlite3_result_double(pContext, pseudo_area);
212
0
}
213
214
// ---------------------------------------------------------------------------
215
216
static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
217
0
                                        int /* argc */, sqlite3_value **argv) {
218
0
    bool b0, b1, b2, b3, b4, b5, b6, b7;
219
0
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
220
0
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
221
0
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
222
0
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
223
0
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
224
0
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
225
0
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
226
0
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
227
0
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
228
0
        sqlite3_result_null(pContext);
229
0
        return;
230
0
    }
231
0
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
232
0
                                                         east_lon1, north_lat1);
233
0
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
234
0
                                                         east_lon2, north_lat2);
235
0
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
236
0
}
237
238
// ---------------------------------------------------------------------------
239
240
class SQLiteHandle {
241
    sqlite3 *sqlite_handle_ = nullptr;
242
    bool close_handle_ = true;
243
244
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
245
    bool is_valid_ = true;
246
#endif
247
248
    int nLayoutVersionMajor_ = 0;
249
    int nLayoutVersionMinor_ = 0;
250
251
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
252
    std::unique_ptr<SQLite3VFS> vfs_{};
253
#endif
254
255
    SQLiteHandle(const SQLiteHandle &) = delete;
256
    SQLiteHandle &operator=(const SQLiteHandle &) = delete;
257
258
    SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
259
0
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
260
0
        assert(sqlite_handle_);
261
0
    }
262
263
    // cppcheck-suppress functionStatic
264
    void initialize();
265
266
    SQLResultSet run(const std::string &sql,
267
                     const ListOfParams &parameters = ListOfParams(),
268
                     bool useMaxFloatPrecision = false);
269
270
  public:
271
    ~SQLiteHandle();
272
273
0
    sqlite3 *handle() { return sqlite_handle_; }
274
275
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
276
0
    bool isValid() const { return is_valid_; }
277
278
0
    void invalidate() { is_valid_ = false; }
279
#endif
280
281
    static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
282
                                              const std::string &path);
283
284
    // might not be shared between thread depending how the handle was opened!
285
    static std::shared_ptr<SQLiteHandle>
286
    initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
287
                     int nLayoutVersionMajor, int nLayoutVersionMinor);
288
289
    static std::unique_ptr<SQLiteHandle>
290
    initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
291
292
    void checkDatabaseLayout(const std::string &mainDbPath,
293
                             const std::string &path,
294
                             const std::string &dbNamePrefix);
295
296
    SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
297
                     const ListOfParams &parameters = ListOfParams(),
298
                     bool useMaxFloatPrecision = false);
299
300
0
    inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
301
0
    inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
302
};
303
304
// ---------------------------------------------------------------------------
305
306
0
SQLiteHandle::~SQLiteHandle() {
307
0
    if (close_handle_) {
308
0
        sqlite3_close(sqlite_handle_);
309
0
    }
310
0
}
311
312
// ---------------------------------------------------------------------------
313
314
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
315
0
                                                 const std::string &path) {
316
317
0
    const int sqlite3VersionNumber = sqlite3_libversion_number();
318
    // Minimum version for correct performance: 3.11
319
0
    if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
320
0
        pj_log(ctx, PJ_LOG_ERROR,
321
0
               "SQLite3 version is %s, whereas at least 3.11 should be used",
322
0
               sqlite3_libversion());
323
0
    }
324
325
0
    std::string vfsName;
326
0
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
327
0
    std::unique_ptr<SQLite3VFS> vfs;
328
0
    if (ctx->custom_sqlite3_vfs_name.empty()) {
329
0
        vfs = SQLite3VFS::create(false, true, true);
330
0
        if (vfs == nullptr) {
331
0
            throw FactoryException("Open of " + path + " failed");
332
0
        }
333
0
        vfsName = vfs->name();
334
0
    } else
335
0
#endif
336
0
    {
337
0
        vfsName = ctx->custom_sqlite3_vfs_name;
338
0
    }
339
0
    sqlite3 *sqlite_handle = nullptr;
340
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
341
0
    if (sqlite3_open_v2(
342
0
            path.c_str(), &sqlite_handle,
343
0
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
344
0
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
345
0
        !sqlite_handle) {
346
0
        if (sqlite_handle != nullptr) {
347
0
            sqlite3_close(sqlite_handle);
348
0
        }
349
0
        throw FactoryException("Open of " + path + " failed");
350
0
    }
351
0
    auto handle =
352
0
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
353
0
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
354
0
    handle->vfs_ = std::move(vfs);
355
0
#endif
356
0
    handle->initialize();
357
0
    handle->checkDatabaseLayout(path, path, std::string());
358
0
    return handle;
359
0
}
360
361
// ---------------------------------------------------------------------------
362
363
std::shared_ptr<SQLiteHandle>
364
SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
365
                               int nLayoutVersionMajor,
366
0
                               int nLayoutVersionMinor) {
367
0
    auto handle = std::shared_ptr<SQLiteHandle>(
368
0
        new SQLiteHandle(sqlite_handle, close_handle));
369
0
    handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
370
0
    handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
371
0
    handle->initialize();
372
0
    return handle;
373
0
}
374
375
// ---------------------------------------------------------------------------
376
377
std::unique_ptr<SQLiteHandle>
378
SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
379
0
                                        bool close_handle) {
380
0
    auto handle = std::unique_ptr<SQLiteHandle>(
381
0
        new SQLiteHandle(sqlite_handle, close_handle));
382
0
    handle->initialize();
383
0
    return handle;
384
0
}
385
386
// ---------------------------------------------------------------------------
387
388
SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
389
                               const ListOfParams &parameters,
390
0
                               bool useMaxFloatPrecision) {
391
0
    int nBindField = 1;
392
0
    for (const auto &param : parameters) {
393
0
        const auto &paramType = param.type();
394
0
        if (paramType == SQLValues::Type::STRING) {
395
0
            const auto &strValue = param.stringValue();
396
0
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
397
0
                              static_cast<int>(strValue.size()),
398
0
                              SQLITE_TRANSIENT);
399
0
        } else if (paramType == SQLValues::Type::INT) {
400
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
401
0
        } else {
402
0
            assert(paramType == SQLValues::Type::DOUBLE);
403
0
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
404
0
        }
405
0
        nBindField++;
406
0
    }
407
408
#ifdef TRACE_DATABASE
409
    size_t nPos = 0;
410
    std::string sqlSubst(sql);
411
    for (const auto &param : parameters) {
412
        nPos = sqlSubst.find('?', nPos);
413
        assert(nPos != std::string::npos);
414
        std::string strValue;
415
        const auto paramType = param.type();
416
        if (paramType == SQLValues::Type::STRING) {
417
            strValue = '\'' + param.stringValue() + '\'';
418
        } else if (paramType == SQLValues::Type::INT) {
419
            strValue = toString(param.intValue());
420
        } else {
421
            strValue = toString(param.doubleValue());
422
        }
423
        sqlSubst =
424
            sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
425
        nPos += strValue.size();
426
    }
427
    logTrace(sqlSubst, "DATABASE");
428
#endif
429
430
0
    SQLResultSet result;
431
0
    const int column_count = sqlite3_column_count(stmt);
432
0
    while (true) {
433
0
        int ret = sqlite3_step(stmt);
434
0
        if (ret == SQLITE_ROW) {
435
0
            SQLRow row(column_count);
436
0
            for (int i = 0; i < column_count; i++) {
437
0
                if (useMaxFloatPrecision &&
438
0
                    sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
439
                    // sqlite3_column_text() does not use maximum precision
440
0
                    std::ostringstream buffer;
441
0
                    buffer.imbue(std::locale::classic());
442
0
                    buffer << std::setprecision(18);
443
0
                    buffer << sqlite3_column_double(stmt, i);
444
0
                    row[i] = buffer.str();
445
0
                } else {
446
0
                    const char *txt = reinterpret_cast<const char *>(
447
0
                        sqlite3_column_text(stmt, i));
448
0
                    if (txt) {
449
0
                        row[i] = txt;
450
0
                    }
451
0
                }
452
0
            }
453
0
            result.emplace_back(std::move(row));
454
0
        } else if (ret == SQLITE_DONE) {
455
0
            break;
456
0
        } else {
457
0
            throw FactoryException("SQLite error on " + sql + ": " +
458
0
                                   sqlite3_errmsg(sqlite_handle_));
459
0
        }
460
0
    }
461
0
    return result;
462
0
}
463
464
// ---------------------------------------------------------------------------
465
466
SQLResultSet SQLiteHandle::run(const std::string &sql,
467
                               const ListOfParams &parameters,
468
0
                               bool useMaxFloatPrecision) {
469
0
    sqlite3_stmt *stmt = nullptr;
470
0
    try {
471
0
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
472
0
                               static_cast<int>(sql.size()), &stmt,
473
0
                               nullptr) != SQLITE_OK) {
474
0
            throw FactoryException("SQLite error on " + sql + ": " +
475
0
                                   sqlite3_errmsg(sqlite_handle_));
476
0
        }
477
0
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
478
0
        sqlite3_finalize(stmt);
479
0
        return ret;
480
0
    } catch (const std::exception &) {
481
0
        if (stmt)
482
0
            sqlite3_finalize(stmt);
483
0
        throw;
484
0
    }
485
0
}
486
487
// ---------------------------------------------------------------------------
488
489
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
490
                                       const std::string &path,
491
0
                                       const std::string &dbNamePrefix) {
492
0
    if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
493
0
                                     "sqlite_master WHERE name = 'metadata'")
494
0
                                     .empty()) {
495
        // Accept auxiliary databases without metadata table (sparse DBs)
496
0
        return;
497
0
    }
498
0
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
499
0
                   "metadata WHERE key IN "
500
0
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
501
0
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
502
0
    if (res.empty() && !dbNamePrefix.empty()) {
503
        // Accept auxiliary databases without layout metadata.
504
0
        return;
505
0
    }
506
0
    if (res.size() != 2) {
507
0
        throw FactoryException(
508
0
            path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
509
0
                   "DATABASE.LAYOUT.VERSION.MINOR "
510
0
                   "metadata. It comes from another PROJ installation.");
511
0
    }
512
0
    int major = 0;
513
0
    int minor = 0;
514
0
    for (const auto &row : res) {
515
0
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
516
0
            major = atoi(row[1].c_str());
517
0
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
518
0
            minor = atoi(row[1].c_str());
519
0
        }
520
0
    }
521
0
    if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
522
0
        throw FactoryException(
523
0
            path +
524
0
            " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
525
0
            " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
526
0
            " is expected. "
527
0
            "It comes from another PROJ installation.");
528
0
    }
529
530
0
    if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
531
0
        throw FactoryException(
532
0
            path +
533
0
            " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
534
0
            " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
535
0
            " is expected. "
536
0
            "It comes from another PROJ installation.");
537
0
    }
538
539
0
    if (dbNamePrefix.empty()) {
540
0
        nLayoutVersionMajor_ = major;
541
0
        nLayoutVersionMinor_ = minor;
542
0
    } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
543
0
        throw FactoryException(
544
0
            "Auxiliary database " + path +
545
0
            " contains a DATABASE.LAYOUT.VERSION =  " + toString(major) + '.' +
546
0
            toString(minor) +
547
0
            " which is different from the one from the main database " +
548
0
            mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
549
0
            toString(nLayoutVersionMinor_));
550
0
    }
551
0
}
552
553
// ---------------------------------------------------------------------------
554
555
#ifndef SQLITE_DETERMINISTIC
556
#define SQLITE_DETERMINISTIC 0
557
#endif
558
559
0
void SQLiteHandle::initialize() {
560
561
    // There is a bug in sqlite 3.38.0 with some complex queries.
562
    // Cf https://github.com/OSGeo/PROJ/issues/3077
563
    // Disabling Bloom-filter pull-down optimization as suggested in
564
    // https://sqlite.org/forum/forumpost/7d3a75438c
565
0
    const int sqlite3VersionNumber = sqlite3_libversion_number();
566
0
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
567
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
568
0
                             0x100000);
569
0
    }
570
571
0
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
572
0
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
573
0
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
574
0
                            nullptr);
575
576
0
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
577
0
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
578
0
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
579
0
}
580
581
// ---------------------------------------------------------------------------
582
583
class SQLiteHandleCache {
584
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
585
    bool firstTime_ = true;
586
#endif
587
588
    std::mutex sMutex_{};
589
590
    // Map dbname to SQLiteHandle
591
    lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
592
593
  public:
594
    static SQLiteHandleCache &get();
595
596
    std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
597
                                            PJ_CONTEXT *ctx);
598
599
    void clear();
600
601
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
602
    void invalidateHandles();
603
#endif
604
};
605
606
// ---------------------------------------------------------------------------
607
608
28.9k
SQLiteHandleCache &SQLiteHandleCache::get() {
609
    // Global cache
610
28.9k
    static SQLiteHandleCache gSQLiteHandleCache;
611
28.9k
    return gSQLiteHandleCache;
612
28.9k
}
613
614
// ---------------------------------------------------------------------------
615
616
28.9k
void SQLiteHandleCache::clear() {
617
28.9k
    std::lock_guard<std::mutex> lock(sMutex_);
618
28.9k
    cache_.clear();
619
28.9k
}
620
621
// ---------------------------------------------------------------------------
622
623
std::shared_ptr<SQLiteHandle>
624
0
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
625
0
    std::lock_guard<std::mutex> lock(sMutex_);
626
627
0
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
628
0
    if (firstTime_) {
629
0
        firstTime_ = false;
630
0
        pthread_atfork(nullptr, nullptr,
631
0
                       []() { SQLiteHandleCache::get().invalidateHandles(); });
632
0
    }
633
0
#endif
634
635
0
    std::shared_ptr<SQLiteHandle> handle;
636
0
    std::string key = path + ctx->custom_sqlite3_vfs_name;
637
0
    if (!cache_.tryGet(key, handle)) {
638
0
        handle = SQLiteHandle::open(ctx, path);
639
0
        cache_.insert(key, handle);
640
0
    }
641
0
    return handle;
642
0
}
643
644
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
645
// ---------------------------------------------------------------------------
646
647
0
void SQLiteHandleCache::invalidateHandles() {
648
0
    std::lock_guard<std::mutex> lock(sMutex_);
649
0
    const auto lambda =
650
0
        [](const lru11::KeyValuePair<std::string, std::shared_ptr<SQLiteHandle>>
651
0
               &kvp) { kvp.value->invalidate(); };
652
0
    cache_.cwalk(lambda);
653
0
    cache_.clear();
654
0
}
655
#endif
656
657
// ---------------------------------------------------------------------------
658
659
struct DatabaseContext::Private {
660
    Private();
661
    ~Private();
662
663
    void open(const std::string &databasePath, PJ_CONTEXT *ctx);
664
    void setHandle(sqlite3 *sqlite_handle);
665
666
    const std::shared_ptr<SQLiteHandle> &handle();
667
668
70.6k
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
669
70.6k
    void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
670
671
    SQLResultSet run(const std::string &sql,
672
                     const ListOfParams &parameters = ListOfParams(),
673
                     bool useMaxFloatPrecision = false);
674
675
    std::vector<std::string> getDatabaseStructure();
676
677
    // cppcheck-suppress functionStatic
678
0
    const std::string &getPath() const { return databasePath_; }
679
680
    void attachExtraDatabases(
681
        const std::vector<std::string> &auxiliaryDatabasePaths);
682
683
    // Mechanism to detect recursion in calls from
684
    // AuthorityFactory::createXXX() -> createFromUserInput() ->
685
    // AuthorityFactory::createXXX()
686
    struct RecursionDetector {
687
        explicit RecursionDetector(const DatabaseContextNNPtr &context)
688
0
            : dbContext_(context) {
689
0
            if (dbContext_->getPrivate()->recLevel_ == 2) {
690
                // Throw exception before incrementing, since the destructor
691
                // will not be called
692
0
                throw FactoryException("Too many recursive calls");
693
0
            }
694
0
            ++dbContext_->getPrivate()->recLevel_;
695
0
        }
696
697
0
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
698
699
      private:
700
        DatabaseContextNNPtr dbContext_;
701
    };
702
703
0
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
704
0
        return mapCanonicalizeGRFName_;
705
0
    }
706
707
    // cppcheck-suppress functionStatic
708
    common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
709
    // cppcheck-suppress functionStatic
710
    void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
711
712
    // cppcheck-suppress functionStatic
713
    crs::CRSPtr getCRSFromCache(const std::string &code);
714
    // cppcheck-suppress functionStatic
715
    void cache(const std::string &code, const crs::CRSNNPtr &crs);
716
717
    datum::GeodeticReferenceFramePtr
718
    // cppcheck-suppress functionStatic
719
    getGeodeticDatumFromCache(const std::string &code);
720
    // cppcheck-suppress functionStatic
721
    void cache(const std::string &code,
722
               const datum::GeodeticReferenceFrameNNPtr &datum);
723
724
    datum::DatumEnsemblePtr
725
    // cppcheck-suppress functionStatic
726
    getDatumEnsembleFromCache(const std::string &code);
727
    // cppcheck-suppress functionStatic
728
    void cache(const std::string &code,
729
               const datum::DatumEnsembleNNPtr &datumEnsemble);
730
731
    datum::EllipsoidPtr
732
    // cppcheck-suppress functionStatic
733
    getEllipsoidFromCache(const std::string &code);
734
    // cppcheck-suppress functionStatic
735
    void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
736
737
    datum::PrimeMeridianPtr
738
    // cppcheck-suppress functionStatic
739
    getPrimeMeridianFromCache(const std::string &code);
740
    // cppcheck-suppress functionStatic
741
    void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
742
743
    // cppcheck-suppress functionStatic
744
    cs::CoordinateSystemPtr
745
    getCoordinateSystemFromCache(const std::string &code);
746
    // cppcheck-suppress functionStatic
747
    void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
748
749
    // cppcheck-suppress functionStatic
750
    metadata::ExtentPtr getExtentFromCache(const std::string &code);
751
    // cppcheck-suppress functionStatic
752
    void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
753
754
    // cppcheck-suppress functionStatic
755
    bool getCRSToCRSCoordOpFromCache(
756
        const std::string &code,
757
        std::vector<operation::CoordinateOperationNNPtr> &list);
758
    // cppcheck-suppress functionStatic
759
    void cache(const std::string &code,
760
               const std::vector<operation::CoordinateOperationNNPtr> &list);
761
762
    struct GridInfoCache {
763
        std::string fullFilename{};
764
        std::string packageName{};
765
        std::string url{};
766
        bool found = false;
767
        bool directDownload = false;
768
        bool openLicense = false;
769
        bool gridAvailable = false;
770
    };
771
772
    // cppcheck-suppress functionStatic
773
    bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
774
    // cppcheck-suppress functionStatic
775
    void cache(const std::string &code, const GridInfoCache &info);
776
777
    struct VersionedAuthName {
778
        std::string versionedAuthName{};
779
        std::string authName{};
780
        std::string version{};
781
        int priority = 0;
782
    };
783
    const std::vector<VersionedAuthName> &getCacheAuthNameWithVersion();
784
785
  private:
786
    friend class DatabaseContext;
787
788
    // This is a manual implementation of std::enable_shared_from_this<> that
789
    // avoids publicly deriving from it.
790
    std::weak_ptr<DatabaseContext> self_{};
791
792
    std::string databasePath_{};
793
    std::vector<std::string> auxiliaryDatabasePaths_{};
794
    std::shared_ptr<SQLiteHandle> sqlite_handle_{};
795
    std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
796
    PJ_CONTEXT *pjCtxt_ = nullptr;
797
    int recLevel_ = 0;
798
    bool detach_ = false;
799
    std::string lastMetadataValue_{};
800
    std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
801
802
    // Used by startInsertStatementsSession() and related functions
803
    std::string memoryDbForInsertPath_{};
804
    std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
805
806
    using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
807
808
    static constexpr size_t CACHE_SIZE = 128;
809
    LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
810
    LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
811
    LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
812
    LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
813
    LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE};
814
    LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
815
    LRUCacheOfObjects cacheCS_{CACHE_SIZE};
816
    LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
817
    lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
818
        cacheCRSToCrsCoordOp_{CACHE_SIZE};
819
    lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
820
821
    std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
822
823
    lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
824
        CACHE_SIZE};
825
826
    std::vector<VersionedAuthName> cacheAuthNameWithVersion_{};
827
828
    static void insertIntoCache(LRUCacheOfObjects &cache,
829
                                const std::string &code,
830
                                const util::BaseObjectPtr &obj);
831
832
    static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
833
                             util::BaseObjectPtr &obj);
834
835
    void closeDB() noexcept;
836
837
    void clearCaches();
838
839
    std::string findFreeCode(const std::string &tableName,
840
                             const std::string &authName,
841
                             const std::string &codePrototype);
842
843
    void identify(const DatabaseContextNNPtr &dbContext,
844
                  const cs::CoordinateSystemNNPtr &obj, std::string &authName,
845
                  std::string &code);
846
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
847
                          const cs::CoordinateSystemNNPtr &obj,
848
                          const std::string &ownerType,
849
                          const std::string &ownerAuthName,
850
                          const std::string &ownerCode, std::string &authName,
851
                          std::string &code,
852
                          std::vector<std::string> &sqlStatements);
853
854
    void identify(const DatabaseContextNNPtr &dbContext,
855
                  const common::UnitOfMeasure &obj, std::string &authName,
856
                  std::string &code);
857
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
858
                          const common::UnitOfMeasure &unit,
859
                          const std::string &ownerAuthName,
860
                          std::string &authName, std::string &code,
861
                          std::vector<std::string> &sqlStatements);
862
863
    void appendSql(std::vector<std::string> &sqlStatements,
864
                   const std::string &sql);
865
866
    void
867
    identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj,
868
                           const std::string &tableName,
869
                           const std::string &authName, const std::string &code,
870
                           const std::vector<std::string> &allowedAuthorities,
871
                           std::vector<std::string> &sqlStatements);
872
873
    std::vector<std::string>
874
    getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm,
875
                           const std::string &authName, const std::string &code,
876
                           bool numericCode,
877
                           const std::vector<std::string> &allowedAuthorities);
878
879
    std::vector<std::string>
880
    getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid,
881
                           const std::string &authName, const std::string &code,
882
                           bool numericCode,
883
                           const std::vector<std::string> &allowedAuthorities);
884
885
    std::vector<std::string>
886
    getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum,
887
                           const std::string &authName, const std::string &code,
888
                           bool numericCode,
889
                           const std::vector<std::string> &allowedAuthorities);
890
891
    std::vector<std::string>
892
    getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble,
893
                           const std::string &authName, const std::string &code,
894
                           bool numericCode,
895
                           const std::vector<std::string> &allowedAuthorities);
896
897
    std::vector<std::string>
898
    getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs,
899
                           const std::string &authName, const std::string &code,
900
                           bool numericCode,
901
                           const std::vector<std::string> &allowedAuthorities);
902
903
    std::vector<std::string>
904
    getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs,
905
                           const std::string &authName, const std::string &code,
906
                           bool numericCode,
907
                           const std::vector<std::string> &allowedAuthorities);
908
909
    std::vector<std::string>
910
    getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum,
911
                           const std::string &authName, const std::string &code,
912
                           bool numericCode,
913
                           const std::vector<std::string> &allowedAuthorities);
914
915
    std::vector<std::string>
916
    getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs,
917
                           const std::string &authName, const std::string &code,
918
                           bool numericCode,
919
                           const std::vector<std::string> &allowedAuthorities);
920
921
    std::vector<std::string>
922
    getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs,
923
                           const std::string &authName, const std::string &code,
924
                           bool numericCode,
925
                           const std::vector<std::string> &allowedAuthorities);
926
927
    Private(const Private &) = delete;
928
    Private &operator=(const Private &) = delete;
929
};
930
931
// ---------------------------------------------------------------------------
932
933
70.6k
DatabaseContext::Private::Private() = default;
934
935
// ---------------------------------------------------------------------------
936
937
70.6k
DatabaseContext::Private::~Private() {
938
70.6k
    assert(recLevel_ == 0);
939
940
70.6k
    closeDB();
941
70.6k
}
942
943
// ---------------------------------------------------------------------------
944
945
70.6k
void DatabaseContext::Private::closeDB() noexcept {
946
947
70.6k
    if (detach_) {
948
        // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
949
        // a crash in TEST(factory, attachExtraDatabases_auxiliary)
950
        // due to possible wrong caching of key info.
951
        // The bug is specific to using a memory file with shared cache as an
952
        // auxiliary DB.
953
        // The fix was likely in 3.8.8
954
        // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
955
        // But just after 3.8.2,
956
        // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
957
        // also seemed to hide the issue.
958
        // Detaching a database hides the issue, not sure if it is by chance...
959
0
        try {
960
0
            run("DETACH DATABASE db_0");
961
0
        } catch (...) {
962
0
        }
963
0
        detach_ = false;
964
0
    }
965
966
70.6k
    for (auto &pair : mapSqlToStatement_) {
967
0
        sqlite3_finalize(pair.second);
968
0
    }
969
70.6k
    mapSqlToStatement_.clear();
970
971
70.6k
    sqlite_handle_.reset();
972
70.6k
}
973
974
// ---------------------------------------------------------------------------
975
976
0
void DatabaseContext::Private::clearCaches() {
977
978
0
    cacheUOM_.clear();
979
0
    cacheCRS_.clear();
980
0
    cacheEllipsoid_.clear();
981
0
    cacheGeodeticDatum_.clear();
982
0
    cacheDatumEnsemble_.clear();
983
0
    cachePrimeMeridian_.clear();
984
0
    cacheCS_.clear();
985
0
    cacheExtent_.clear();
986
0
    cacheCRSToCrsCoordOp_.clear();
987
0
    cacheGridInfo_.clear();
988
0
    cacheAllowedAuthorities_.clear();
989
0
    cacheAliasNames_.clear();
990
0
}
991
992
// ---------------------------------------------------------------------------
993
994
0
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
995
0
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
996
0
    if (sqlite_handle_ && !sqlite_handle_->isValid()) {
997
0
        closeDB();
998
0
        open(databasePath_, pjCtxt_);
999
0
        if (!auxiliaryDatabasePaths_.empty()) {
1000
0
            attachExtraDatabases(auxiliaryDatabasePaths_);
1001
0
        }
1002
0
    }
1003
0
#endif
1004
0
    return sqlite_handle_;
1005
0
}
1006
1007
// ---------------------------------------------------------------------------
1008
1009
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1010
                                               const std::string &code,
1011
0
                                               const util::BaseObjectPtr &obj) {
1012
0
    cache.insert(code, obj);
1013
0
}
1014
1015
// ---------------------------------------------------------------------------
1016
1017
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1018
                                            const std::string &code,
1019
0
                                            util::BaseObjectPtr &obj) {
1020
0
    cache.tryGet(code, obj);
1021
0
}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1026
    const std::string &code,
1027
0
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1028
0
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1029
0
}
1030
1031
// ---------------------------------------------------------------------------
1032
1033
void DatabaseContext::Private::cache(
1034
    const std::string &code,
1035
0
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1036
0
    cacheCRSToCrsCoordOp_.insert(code, list);
1037
0
}
1038
1039
// ---------------------------------------------------------------------------
1040
1041
0
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1042
0
    util::BaseObjectPtr obj;
1043
0
    getFromCache(cacheCRS_, code, obj);
1044
0
    return std::static_pointer_cast<crs::CRS>(obj);
1045
0
}
1046
1047
// ---------------------------------------------------------------------------
1048
1049
void DatabaseContext::Private::cache(const std::string &code,
1050
0
                                     const crs::CRSNNPtr &crs) {
1051
0
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1052
0
}
1053
1054
// ---------------------------------------------------------------------------
1055
1056
common::UnitOfMeasurePtr
1057
0
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1058
0
    util::BaseObjectPtr obj;
1059
0
    getFromCache(cacheUOM_, code, obj);
1060
0
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1061
0
}
1062
1063
// ---------------------------------------------------------------------------
1064
1065
void DatabaseContext::Private::cache(const std::string &code,
1066
0
                                     const common::UnitOfMeasureNNPtr &uom) {
1067
0
    insertIntoCache(cacheUOM_, code, uom.as_nullable());
1068
0
}
1069
1070
// ---------------------------------------------------------------------------
1071
1072
datum::GeodeticReferenceFramePtr
1073
0
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1074
0
    util::BaseObjectPtr obj;
1075
0
    getFromCache(cacheGeodeticDatum_, code, obj);
1076
0
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1077
0
}
1078
1079
// ---------------------------------------------------------------------------
1080
1081
void DatabaseContext::Private::cache(
1082
0
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1083
0
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1084
0
}
1085
1086
// ---------------------------------------------------------------------------
1087
1088
datum::DatumEnsemblePtr
1089
0
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1090
0
    util::BaseObjectPtr obj;
1091
0
    getFromCache(cacheDatumEnsemble_, code, obj);
1092
0
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1093
0
}
1094
1095
// ---------------------------------------------------------------------------
1096
1097
void DatabaseContext::Private::cache(
1098
0
    const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) {
1099
0
    insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable());
1100
0
}
1101
1102
// ---------------------------------------------------------------------------
1103
1104
datum::EllipsoidPtr
1105
0
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1106
0
    util::BaseObjectPtr obj;
1107
0
    getFromCache(cacheEllipsoid_, code, obj);
1108
0
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1109
0
}
1110
1111
// ---------------------------------------------------------------------------
1112
1113
void DatabaseContext::Private::cache(const std::string &code,
1114
0
                                     const datum::EllipsoidNNPtr &ellps) {
1115
0
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1116
0
}
1117
1118
// ---------------------------------------------------------------------------
1119
1120
datum::PrimeMeridianPtr
1121
0
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1122
0
    util::BaseObjectPtr obj;
1123
0
    getFromCache(cachePrimeMeridian_, code, obj);
1124
0
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1125
0
}
1126
1127
// ---------------------------------------------------------------------------
1128
1129
void DatabaseContext::Private::cache(const std::string &code,
1130
0
                                     const datum::PrimeMeridianNNPtr &pm) {
1131
0
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1132
0
}
1133
1134
// ---------------------------------------------------------------------------
1135
1136
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1137
0
    const std::string &code) {
1138
0
    util::BaseObjectPtr obj;
1139
0
    getFromCache(cacheCS_, code, obj);
1140
0
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1141
0
}
1142
1143
// ---------------------------------------------------------------------------
1144
1145
void DatabaseContext::Private::cache(const std::string &code,
1146
0
                                     const cs::CoordinateSystemNNPtr &cs) {
1147
0
    insertIntoCache(cacheCS_, code, cs.as_nullable());
1148
0
}
1149
1150
// ---------------------------------------------------------------------------
1151
1152
metadata::ExtentPtr
1153
0
DatabaseContext::Private::getExtentFromCache(const std::string &code) {
1154
0
    util::BaseObjectPtr obj;
1155
0
    getFromCache(cacheExtent_, code, obj);
1156
0
    return std::static_pointer_cast<metadata::Extent>(obj);
1157
0
}
1158
1159
// ---------------------------------------------------------------------------
1160
1161
void DatabaseContext::Private::cache(const std::string &code,
1162
0
                                     const metadata::ExtentNNPtr &extent) {
1163
0
    insertIntoCache(cacheExtent_, code, extent.as_nullable());
1164
0
}
1165
1166
// ---------------------------------------------------------------------------
1167
1168
bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
1169
0
                                                    GridInfoCache &info) {
1170
0
    return cacheGridInfo_.tryGet(code, info);
1171
0
}
1172
1173
// ---------------------------------------------------------------------------
1174
1175
void DatabaseContext::Private::cache(const std::string &code,
1176
0
                                     const GridInfoCache &info) {
1177
0
    cacheGridInfo_.insert(code, info);
1178
0
}
1179
1180
// ---------------------------------------------------------------------------
1181
1182
void DatabaseContext::Private::open(const std::string &databasePath,
1183
70.6k
                                    PJ_CONTEXT *ctx) {
1184
70.6k
    if (!ctx) {
1185
0
        ctx = pj_get_default_ctx();
1186
0
    }
1187
1188
70.6k
    setPjCtxt(ctx);
1189
70.6k
    std::string path(databasePath);
1190
70.6k
    if (path.empty()) {
1191
70.6k
        path.resize(2048);
1192
70.6k
        const bool found =
1193
70.6k
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
1194
70.6k
        path.resize(strlen(path.c_str()));
1195
70.6k
        if (!found) {
1196
70.6k
            throw FactoryException("Cannot find proj.db");
1197
70.6k
        }
1198
70.6k
    }
1199
1200
0
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1201
1202
0
    databasePath_ = std::move(path);
1203
0
}
1204
1205
// ---------------------------------------------------------------------------
1206
1207
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1208
1209
0
    assert(sqlite_handle);
1210
0
    assert(!sqlite_handle_);
1211
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1212
0
}
1213
1214
// ---------------------------------------------------------------------------
1215
1216
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1217
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1218
0
                                           memoryDbForInsertPath_.empty()
1219
0
                                       ? ""
1220
0
                                       : "db_0.");
1221
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1222
0
                        "sqlite_master WHERE type = ");
1223
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1224
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1225
0
    std::vector<std::string> res;
1226
0
    for (const auto &objectType : objectTypes) {
1227
0
        const auto sqlRes = run(sqlBegin + objectType);
1228
0
        for (const auto &row : sqlRes) {
1229
0
            res.emplace_back(row[0]);
1230
0
        }
1231
0
    }
1232
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1233
0
        res.emplace_back(
1234
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1235
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1236
0
        res.emplace_back(
1237
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1238
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1239
0
    }
1240
0
    return res;
1241
0
}
1242
1243
// ---------------------------------------------------------------------------
1244
1245
void DatabaseContext::Private::attachExtraDatabases(
1246
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1247
1248
0
    auto l_handle = handle();
1249
0
    assert(l_handle);
1250
1251
0
    auto tables =
1252
0
        run("SELECT name FROM sqlite_master WHERE type IN ('table', 'view') "
1253
0
            "AND name NOT LIKE 'sqlite_stat%'");
1254
0
    std::map<std::string, std::vector<std::string>> tableStructure;
1255
0
    for (const auto &rowTable : tables) {
1256
0
        const auto &tableName = rowTable[0];
1257
0
        auto tableInfo = run("PRAGMA table_info(\"" +
1258
0
                             replaceAll(tableName, "\"", "\"\"") + "\")");
1259
0
        for (const auto &rowCol : tableInfo) {
1260
0
            const auto &colName = rowCol[1];
1261
0
            tableStructure[tableName].push_back(colName);
1262
0
        }
1263
0
    }
1264
1265
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1266
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1267
1268
0
    closeDB();
1269
0
    if (auxiliaryDatabasePaths.empty()) {
1270
0
        open(databasePath_, pjCtxt());
1271
0
        return;
1272
0
    }
1273
1274
0
    sqlite3 *sqlite_handle = nullptr;
1275
0
    sqlite3_open_v2(
1276
0
        ":memory:", &sqlite_handle,
1277
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1278
0
    if (!sqlite_handle) {
1279
0
        throw FactoryException("cannot create in memory database");
1280
0
    }
1281
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1282
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1283
0
    l_handle = sqlite_handle_;
1284
1285
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1286
0
    detach_ = true;
1287
0
    int count = 1;
1288
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1289
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1290
0
        std::string sql = "ATTACH DATABASE ? AS ";
1291
0
        sql += attachedDbName;
1292
0
        count++;
1293
0
        run(sql, {otherDbPath});
1294
1295
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1296
0
                                      attachedDbName + '.');
1297
0
    }
1298
1299
0
    for (const auto &pair : tableStructure) {
1300
0
        std::string sql("CREATE TEMP VIEW ");
1301
0
        sql += pair.first;
1302
0
        sql += " AS ";
1303
0
        for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1304
0
            std::string selectFromAux("SELECT ");
1305
0
            bool firstCol = true;
1306
0
            for (const auto &colName : pair.second) {
1307
0
                if (!firstCol) {
1308
0
                    selectFromAux += ", ";
1309
0
                }
1310
0
                firstCol = false;
1311
0
                selectFromAux += colName;
1312
0
            }
1313
0
            selectFromAux += " FROM db_";
1314
0
            selectFromAux += toString(static_cast<int>(i));
1315
0
            selectFromAux += ".";
1316
0
            selectFromAux += pair.first;
1317
1318
0
            try {
1319
                // Check that the request will succeed. In case of 'sparse'
1320
                // databases...
1321
0
                run(selectFromAux + " LIMIT 0");
1322
1323
0
                if (i > 0) {
1324
0
                    sql += " UNION ALL ";
1325
0
                }
1326
0
                sql += selectFromAux;
1327
0
            } catch (const std::exception &) {
1328
0
            }
1329
0
        }
1330
0
        run(sql);
1331
0
    }
1332
0
}
1333
1334
// ---------------------------------------------------------------------------
1335
1336
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1337
                                           const ListOfParams &parameters,
1338
0
                                           bool useMaxFloatPrecision) {
1339
1340
0
    auto l_handle = handle();
1341
0
    assert(l_handle);
1342
1343
0
    sqlite3_stmt *stmt = nullptr;
1344
0
    auto iter = mapSqlToStatement_.find(sql);
1345
0
    if (iter != mapSqlToStatement_.end()) {
1346
0
        stmt = iter->second;
1347
0
        sqlite3_reset(stmt);
1348
0
    } else {
1349
0
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1350
0
                               static_cast<int>(sql.size()), &stmt,
1351
0
                               nullptr) != SQLITE_OK) {
1352
0
            throw FactoryException("SQLite error on " + sql + ": " +
1353
0
                                   sqlite3_errmsg(l_handle->handle()));
1354
0
        }
1355
0
        mapSqlToStatement_.insert(
1356
0
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1357
0
    }
1358
1359
0
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1360
0
}
1361
1362
// ---------------------------------------------------------------------------
1363
1364
0
static std::string formatStatement(const char *fmt, ...) {
1365
0
    std::string res;
1366
0
    va_list args;
1367
0
    va_start(args, fmt);
1368
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1369
0
        if (fmt[i] == '%') {
1370
0
            if (fmt[i + 1] == '%') {
1371
0
                res += '%';
1372
0
            } else if (fmt[i + 1] == 'q') {
1373
0
                const char *arg = va_arg(args, const char *);
1374
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1375
0
                    if (arg[j] == '\'')
1376
0
                        res += arg[j];
1377
0
                    res += arg[j];
1378
0
                }
1379
0
            } else if (fmt[i + 1] == 'Q') {
1380
0
                const char *arg = va_arg(args, const char *);
1381
0
                if (arg == nullptr)
1382
0
                    res += "NULL";
1383
0
                else {
1384
0
                    res += '\'';
1385
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1386
0
                        if (arg[j] == '\'')
1387
0
                            res += arg[j];
1388
0
                        res += arg[j];
1389
0
                    }
1390
0
                    res += '\'';
1391
0
                }
1392
0
            } else if (fmt[i + 1] == 's') {
1393
0
                const char *arg = va_arg(args, const char *);
1394
0
                res += arg;
1395
0
            } else if (fmt[i + 1] == 'f') {
1396
0
                const double arg = va_arg(args, double);
1397
0
                res += toString(arg);
1398
0
            } else if (fmt[i + 1] == 'd') {
1399
0
                const int arg = va_arg(args, int);
1400
0
                res += toString(arg);
1401
0
            } else {
1402
0
                va_end(args);
1403
0
                throw FactoryException(
1404
0
                    "Unsupported formatter in formatStatement()");
1405
0
            }
1406
0
            ++i;
1407
0
        } else {
1408
0
            res += fmt[i];
1409
0
        }
1410
0
    }
1411
0
    va_end(args);
1412
0
    return res;
1413
0
}
1414
1415
// ---------------------------------------------------------------------------
1416
1417
void DatabaseContext::Private::appendSql(
1418
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1419
0
    sqlStatements.emplace_back(sql);
1420
0
    char *errMsg = nullptr;
1421
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1422
0
                     &errMsg) != SQLITE_OK) {
1423
0
        std::string s("Cannot execute " + sql);
1424
0
        if (errMsg) {
1425
0
            s += " : ";
1426
0
            s += errMsg;
1427
0
        }
1428
0
        sqlite3_free(errMsg);
1429
0
        throw FactoryException(s);
1430
0
    }
1431
0
    sqlite3_free(errMsg);
1432
0
}
1433
1434
// ---------------------------------------------------------------------------
1435
1436
static void identifyFromNameOrCode(
1437
    const DatabaseContextNNPtr &dbContext,
1438
    const std::vector<std::string> &allowedAuthorities,
1439
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1440
    std::function<std::shared_ptr<util::IComparable>(
1441
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1442
        instantiateFunc,
1443
    AuthorityFactory::ObjectType objType, std::string &authName,
1444
0
    std::string &code) {
1445
1446
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1447
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1448
1449
0
    for (const auto &id : obj->identifiers()) {
1450
0
        try {
1451
0
            const auto &idAuthName = *(id->codeSpace());
1452
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1453
0
                          allowedAuthoritiesTmp.end(),
1454
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1455
0
                const auto factory =
1456
0
                    AuthorityFactory::create(dbContext, idAuthName);
1457
0
                if (instantiateFunc(factory, id->code())
1458
0
                        ->isEquivalentTo(
1459
0
                            obj.get(),
1460
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1461
0
                    authName = idAuthName;
1462
0
                    code = id->code();
1463
0
                    return;
1464
0
                }
1465
0
            }
1466
0
        } catch (const std::exception &) {
1467
0
        }
1468
0
    }
1469
1470
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1471
0
        const auto factory =
1472
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1473
0
        const auto candidates =
1474
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1475
0
        for (const auto &candidate : candidates) {
1476
0
            const auto &ids = candidate->identifiers();
1477
0
            if (!ids.empty() &&
1478
0
                candidate->isEquivalentTo(
1479
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1480
0
                const auto &id = ids.front();
1481
0
                authName = *(id->codeSpace());
1482
0
                code = id->code();
1483
0
                return;
1484
0
            }
1485
0
        }
1486
0
    }
1487
0
}
1488
1489
// ---------------------------------------------------------------------------
1490
1491
static void
1492
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1493
                       const std::vector<std::string> &allowedAuthorities,
1494
                       const std::string &authNameParent,
1495
                       const datum::DatumEnsembleNNPtr &obj,
1496
0
                       std::string &authName, std::string &code) {
1497
0
    const char *type = "geodetic_datum";
1498
0
    if (!obj->datums().empty() &&
1499
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1500
0
            obj->datums().front().get())) {
1501
0
        type = "vertical_datum";
1502
0
    }
1503
0
    const auto instantiateFunc =
1504
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1505
0
                const std::string &lCode) {
1506
0
            return util::nn_static_pointer_cast<util::IComparable>(
1507
0
                authFactory->createDatumEnsemble(lCode, type));
1508
0
        };
1509
0
    identifyFromNameOrCode(
1510
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1511
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1512
0
}
1513
1514
// ---------------------------------------------------------------------------
1515
1516
static void
1517
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1518
                       const std::vector<std::string> &allowedAuthorities,
1519
                       const std::string &authNameParent,
1520
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1521
0
                       std::string &authName, std::string &code) {
1522
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1523
0
                                    const std::string &lCode) {
1524
0
        return util::nn_static_pointer_cast<util::IComparable>(
1525
0
            authFactory->createGeodeticDatum(lCode));
1526
0
    };
1527
0
    identifyFromNameOrCode(
1528
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1529
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1530
0
}
1531
1532
// ---------------------------------------------------------------------------
1533
1534
static void
1535
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1536
                       const std::vector<std::string> &allowedAuthorities,
1537
                       const std::string &authNameParent,
1538
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1539
0
                       std::string &code) {
1540
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1541
0
                                    const std::string &lCode) {
1542
0
        return util::nn_static_pointer_cast<util::IComparable>(
1543
0
            authFactory->createEllipsoid(lCode));
1544
0
    };
1545
0
    identifyFromNameOrCode(
1546
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1547
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1548
0
}
1549
1550
// ---------------------------------------------------------------------------
1551
1552
static void
1553
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1554
                       const std::vector<std::string> &allowedAuthorities,
1555
                       const std::string &authNameParent,
1556
                       const datum::PrimeMeridianNNPtr &obj,
1557
0
                       std::string &authName, std::string &code) {
1558
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1559
0
                                    const std::string &lCode) {
1560
0
        return util::nn_static_pointer_cast<util::IComparable>(
1561
0
            authFactory->createPrimeMeridian(lCode));
1562
0
    };
1563
0
    identifyFromNameOrCode(
1564
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1565
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1566
0
}
1567
1568
// ---------------------------------------------------------------------------
1569
1570
static void
1571
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1572
                       const std::vector<std::string> &allowedAuthorities,
1573
                       const std::string &authNameParent,
1574
                       const datum::VerticalReferenceFrameNNPtr &obj,
1575
0
                       std::string &authName, std::string &code) {
1576
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1577
0
                                    const std::string &lCode) {
1578
0
        return util::nn_static_pointer_cast<util::IComparable>(
1579
0
            authFactory->createVerticalDatum(lCode));
1580
0
    };
1581
0
    identifyFromNameOrCode(
1582
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1583
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1584
0
}
1585
1586
// ---------------------------------------------------------------------------
1587
1588
static void
1589
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1590
                       const std::vector<std::string> &allowedAuthorities,
1591
                       const std::string &authNameParent,
1592
                       const datum::DatumNNPtr &obj, std::string &authName,
1593
0
                       std::string &code) {
1594
0
    if (const auto geodeticDatum =
1595
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1596
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1597
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1598
0
    } else if (const auto verticalDatum =
1599
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1600
0
                       obj)) {
1601
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1602
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1603
0
    } else {
1604
0
        throw FactoryException("Unhandled type of datum");
1605
0
    }
1606
0
}
1607
1608
// ---------------------------------------------------------------------------
1609
1610
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1611
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1612
0
        return CS_TYPE_ELLIPSOIDAL;
1613
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1614
0
        return CS_TYPE_CARTESIAN;
1615
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1616
0
        return CS_TYPE_VERTICAL;
1617
0
    }
1618
0
    return nullptr;
1619
0
}
1620
1621
// ---------------------------------------------------------------------------
1622
1623
std::string
1624
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1625
                                       const std::string &authName,
1626
0
                                       const std::string &codePrototype) {
1627
0
    std::string code(codePrototype);
1628
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1629
0
            {authName, code})
1630
0
            .empty()) {
1631
0
        return code;
1632
0
    }
1633
1634
0
    for (int counter = 2; counter < 10; counter++) {
1635
0
        code = codePrototype + '_' + toString(counter);
1636
0
        if (run("SELECT 1 FROM " + tableName +
1637
0
                    " WHERE auth_name = ? AND code = ?",
1638
0
                {authName, code})
1639
0
                .empty()) {
1640
0
            return code;
1641
0
        }
1642
0
    }
1643
1644
    // shouldn't happen hopefully...
1645
0
    throw FactoryException("Cannot insert " + tableName +
1646
0
                           ": too many similar codes");
1647
0
}
1648
1649
// ---------------------------------------------------------------------------
1650
1651
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1652
0
    switch (unit.type()) {
1653
0
    case common::UnitOfMeasure::Type::LINEAR:
1654
0
        return "length";
1655
1656
0
    case common::UnitOfMeasure::Type::ANGULAR:
1657
0
        return "angle";
1658
1659
0
    case common::UnitOfMeasure::Type::SCALE:
1660
0
        return "scale";
1661
1662
0
    case common::UnitOfMeasure::Type::TIME:
1663
0
        return "time";
1664
1665
0
    default:
1666
0
        break;
1667
0
    }
1668
0
    return nullptr;
1669
0
}
1670
1671
// ---------------------------------------------------------------------------
1672
1673
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1674
                                        const common::UnitOfMeasure &obj,
1675
                                        std::string &authName,
1676
0
                                        std::string &code) {
1677
    // Identify quickly a few well-known units
1678
0
    const double convFactor = obj.conversionToSI();
1679
0
    switch (obj.type()) {
1680
0
    case common::UnitOfMeasure::Type::LINEAR: {
1681
0
        if (convFactor == 1.0) {
1682
0
            authName = metadata::Identifier::EPSG;
1683
0
            code = "9001";
1684
0
            return;
1685
0
        }
1686
0
        break;
1687
0
    }
1688
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1689
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1690
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1691
0
            1e-10 * CONV_FACTOR_DEGREE) {
1692
0
            authName = metadata::Identifier::EPSG;
1693
0
            code = "9102";
1694
0
            return;
1695
0
        }
1696
0
        break;
1697
0
    }
1698
0
    case common::UnitOfMeasure::Type::SCALE: {
1699
0
        if (convFactor == 1.0) {
1700
0
            authName = metadata::Identifier::EPSG;
1701
0
            code = "9201";
1702
0
            return;
1703
0
        }
1704
0
        break;
1705
0
    }
1706
0
    default:
1707
0
        break;
1708
0
    }
1709
1710
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1711
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1712
0
    ListOfParams params{convFactor};
1713
0
    const char *type = getUnitDatabaseType(obj);
1714
0
    if (type) {
1715
0
        sql += " AND type = ?";
1716
0
        params.emplace_back(std::string(type));
1717
0
    }
1718
0
    sql += " ORDER BY auth_name, code";
1719
0
    const auto res = run(sql, params);
1720
0
    for (const auto &row : res) {
1721
0
        const auto &rowAuthName = row[0];
1722
0
        const auto &rowCode = row[1];
1723
0
        const auto tmpAuthFactory =
1724
0
            AuthorityFactory::create(dbContext, rowAuthName);
1725
0
        try {
1726
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1727
0
            authName = rowAuthName;
1728
0
            code = rowCode;
1729
0
            return;
1730
0
        } catch (const std::exception &) {
1731
0
        }
1732
0
    }
1733
0
}
1734
1735
// ---------------------------------------------------------------------------
1736
1737
void DatabaseContext::Private::identifyOrInsert(
1738
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1739
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1740
0
    std::vector<std::string> &sqlStatements) {
1741
0
    authName = unit.codeSpace();
1742
0
    code = unit.code();
1743
0
    if (authName.empty()) {
1744
0
        identify(dbContext, unit, authName, code);
1745
0
    }
1746
0
    if (!authName.empty()) {
1747
0
        return;
1748
0
    }
1749
0
    const char *type = getUnitDatabaseType(unit);
1750
0
    if (type == nullptr) {
1751
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1752
0
    }
1753
1754
    // Insert new record
1755
0
    authName = ownerAuthName;
1756
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1757
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1758
1759
0
    const auto sql = formatStatement(
1760
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1761
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1762
0
        unit.conversionToSI());
1763
0
    appendSql(sqlStatements, sql);
1764
0
}
1765
1766
// ---------------------------------------------------------------------------
1767
1768
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1769
                                        const cs::CoordinateSystemNNPtr &obj,
1770
                                        std::string &authName,
1771
0
                                        std::string &code) {
1772
1773
0
    const auto &axisList = obj->axisList();
1774
0
    if (axisList.size() == 1U &&
1775
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1776
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1777
0
        (axisList[0]->nameStr() == "Up" ||
1778
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1779
        // preferred coordinate system for gravity-related height
1780
0
        authName = metadata::Identifier::EPSG;
1781
0
        code = "6499";
1782
0
        return;
1783
0
    }
1784
1785
0
    std::string sql(
1786
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1787
0
    ListOfParams params{static_cast<int>(axisList.size())};
1788
0
    const char *type = getCSDatabaseType(obj);
1789
0
    if (type) {
1790
0
        sql += " AND type = ?";
1791
0
        params.emplace_back(std::string(type));
1792
0
    }
1793
0
    sql += " ORDER BY auth_name, code";
1794
0
    const auto res = run(sql, params);
1795
0
    for (const auto &row : res) {
1796
0
        const auto &rowAuthName = row[0];
1797
0
        const auto &rowCode = row[1];
1798
0
        const auto tmpAuthFactory =
1799
0
            AuthorityFactory::create(dbContext, rowAuthName);
1800
0
        try {
1801
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1802
0
            if (cs->_isEquivalentTo(obj.get(),
1803
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1804
0
                authName = rowAuthName;
1805
0
                code = rowCode;
1806
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1807
                    // preferred coordinate system for cartesian
1808
                    // Easting, Northing
1809
0
                    return;
1810
0
                }
1811
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1812
                    // preferred coordinate system for geographic lat, long
1813
0
                    return;
1814
0
                }
1815
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1816
                    // preferred coordinate system for geographic lat, long, h
1817
0
                    return;
1818
0
                }
1819
0
            }
1820
0
        } catch (const std::exception &) {
1821
0
        }
1822
0
    }
1823
0
}
1824
1825
// ---------------------------------------------------------------------------
1826
1827
void DatabaseContext::Private::identifyOrInsert(
1828
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1829
    const std::string &ownerType, const std::string &ownerAuthName,
1830
    const std::string &ownerCode, std::string &authName, std::string &code,
1831
0
    std::vector<std::string> &sqlStatements) {
1832
1833
0
    identify(dbContext, obj, authName, code);
1834
0
    if (!authName.empty()) {
1835
0
        return;
1836
0
    }
1837
1838
0
    const char *type = getCSDatabaseType(obj);
1839
0
    if (type == nullptr) {
1840
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1841
0
    }
1842
1843
    // Insert new record in coordinate_system
1844
0
    authName = ownerAuthName;
1845
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1846
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1847
1848
0
    const auto &axisList = obj->axisList();
1849
0
    {
1850
0
        const auto sql = formatStatement(
1851
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1852
0
            authName.c_str(), code.c_str(), type,
1853
0
            static_cast<int>(axisList.size()));
1854
0
        appendSql(sqlStatements, sql);
1855
0
    }
1856
1857
    // Insert new records for the axis
1858
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1859
0
        const auto &axis = axisList[i];
1860
0
        std::string uomAuthName;
1861
0
        std::string uomCode;
1862
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1863
0
                         uomCode, sqlStatements);
1864
0
        const auto sql = formatStatement(
1865
0
            "INSERT INTO axis VALUES("
1866
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1867
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1868
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1869
0
            axis->direction().toString().c_str(), authName.c_str(),
1870
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1871
0
        appendSql(sqlStatements, sql);
1872
0
    }
1873
0
}
1874
1875
// ---------------------------------------------------------------------------
1876
1877
static void
1878
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1879
                          const std::string &authName, std::string &sql,
1880
0
                          ListOfParams &params) {
1881
0
    sql += "auth_name IN (?";
1882
0
    params.emplace_back(authName);
1883
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1884
0
        sql += ",?";
1885
0
        params.emplace_back(allowedAuthority);
1886
0
    }
1887
0
    sql += ')';
1888
0
}
1889
1890
// ---------------------------------------------------------------------------
1891
1892
void DatabaseContext::Private::identifyOrInsertUsages(
1893
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1894
    const std::string &authName, const std::string &code,
1895
    const std::vector<std::string> &allowedAuthorities,
1896
0
    std::vector<std::string> &sqlStatements) {
1897
1898
0
    std::string usageCode("USAGE_");
1899
0
    const std::string upperTableName(toupper(tableName));
1900
0
    if (!starts_with(code, upperTableName)) {
1901
0
        usageCode += upperTableName;
1902
0
        usageCode += '_';
1903
0
    }
1904
0
    usageCode += code;
1905
1906
0
    const auto &domains = obj->domains();
1907
0
    if (domains.empty()) {
1908
0
        const auto sql =
1909
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
1910
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
1911
0
                            authName.c_str(), usageCode.c_str(),
1912
0
                            tableName.c_str(), authName.c_str(), code.c_str());
1913
0
        appendSql(sqlStatements, sql);
1914
0
        return;
1915
0
    }
1916
1917
0
    int usageCounter = 1;
1918
0
    for (const auto &domain : domains) {
1919
0
        std::string scopeAuthName;
1920
0
        std::string scopeCode;
1921
0
        const auto &scope = domain->scope();
1922
0
        if (scope.has_value()) {
1923
0
            std::string sql =
1924
0
                "SELECT auth_name, code, "
1925
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
1926
0
                "AS order_idx "
1927
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
1928
0
            ListOfParams params{*scope};
1929
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
1930
0
                                      params);
1931
0
            sql += " ORDER BY order_idx, auth_name, code";
1932
0
            const auto rows = run(sql, params);
1933
0
            if (!rows.empty()) {
1934
0
                const auto &row = rows.front();
1935
0
                scopeAuthName = row[0];
1936
0
                scopeCode = row[1];
1937
0
            } else {
1938
0
                scopeAuthName = authName;
1939
0
                scopeCode = "SCOPE_";
1940
0
                scopeCode += tableName;
1941
0
                scopeCode += '_';
1942
0
                scopeCode += code;
1943
0
                const auto sqlToInsert = formatStatement(
1944
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
1945
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
1946
0
                appendSql(sqlStatements, sqlToInsert);
1947
0
            }
1948
0
        } else {
1949
0
            scopeAuthName = "PROJ";
1950
0
            scopeCode = "SCOPE_UNKNOWN";
1951
0
        }
1952
1953
0
        std::string extentAuthName("PROJ");
1954
0
        std::string extentCode("EXTENT_UNKNOWN");
1955
0
        const auto &extent = domain->domainOfValidity();
1956
0
        if (extent) {
1957
0
            const auto &geogElts = extent->geographicElements();
1958
0
            if (!geogElts.empty()) {
1959
0
                const auto bbox =
1960
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
1961
0
                        geogElts.front().get());
1962
0
                if (bbox) {
1963
0
                    std::string sql =
1964
0
                        "SELECT auth_name, code, "
1965
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
1966
0
                        "AS order_idx "
1967
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
1968
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
1969
0
                        "AND ";
1970
0
                    ListOfParams params{
1971
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
1972
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
1973
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
1974
0
                                              params);
1975
0
                    sql += " ORDER BY order_idx, auth_name, code";
1976
0
                    const auto rows = run(sql, params);
1977
0
                    if (!rows.empty()) {
1978
0
                        const auto &row = rows.front();
1979
0
                        extentAuthName = row[0];
1980
0
                        extentCode = row[1];
1981
0
                    } else {
1982
0
                        extentAuthName = authName;
1983
0
                        extentCode = "EXTENT_";
1984
0
                        extentCode += tableName;
1985
0
                        extentCode += '_';
1986
0
                        extentCode += code;
1987
0
                        std::string description(*(extent->description()));
1988
0
                        if (description.empty()) {
1989
0
                            description = "unknown";
1990
0
                        }
1991
0
                        const auto sqlToInsert = formatStatement(
1992
0
                            "INSERT INTO extent "
1993
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
1994
0
                            extentAuthName.c_str(), extentCode.c_str(),
1995
0
                            description.c_str(), description.c_str(),
1996
0
                            bbox->southBoundLatitude(),
1997
0
                            bbox->northBoundLatitude(),
1998
0
                            bbox->westBoundLongitude(),
1999
0
                            bbox->eastBoundLongitude());
2000
0
                        appendSql(sqlStatements, sqlToInsert);
2001
0
                    }
2002
0
                }
2003
0
            }
2004
0
        }
2005
2006
0
        if (domains.size() > 1) {
2007
0
            usageCode += '_';
2008
0
            usageCode += toString(usageCounter);
2009
0
        }
2010
0
        const auto sql = formatStatement(
2011
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2012
0
            "'%q','%q','%q','%q');",
2013
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2014
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2015
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2016
0
        appendSql(sqlStatements, sql);
2017
2018
0
        usageCounter++;
2019
0
    }
2020
0
}
2021
2022
// ---------------------------------------------------------------------------
2023
2024
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2025
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2026
    const std::string &code, bool /*numericCode*/,
2027
0
    const std::vector<std::string> &allowedAuthorities) {
2028
2029
0
    const auto self = NN_NO_CHECK(self_.lock());
2030
2031
    // Check if the object is already known under that code
2032
0
    std::string pmAuthName;
2033
0
    std::string pmCode;
2034
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2035
0
                           pmCode);
2036
0
    if (pmAuthName == authName && pmCode == code) {
2037
0
        return {};
2038
0
    }
2039
2040
0
    std::vector<std::string> sqlStatements;
2041
2042
    // Insert new record in prime_meridian table
2043
0
    std::string uomAuthName;
2044
0
    std::string uomCode;
2045
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2046
0
                     uomCode, sqlStatements);
2047
2048
0
    const auto sql = formatStatement(
2049
0
        "INSERT INTO prime_meridian VALUES("
2050
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2051
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2052
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2053
0
    appendSql(sqlStatements, sql);
2054
2055
0
    return sqlStatements;
2056
0
}
2057
2058
// ---------------------------------------------------------------------------
2059
2060
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2061
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2062
    const std::string &code, bool /*numericCode*/,
2063
0
    const std::vector<std::string> &allowedAuthorities) {
2064
2065
0
    const auto self = NN_NO_CHECK(self_.lock());
2066
2067
    // Check if the object is already known under that code
2068
0
    std::string ellipsoidAuthName;
2069
0
    std::string ellipsoidCode;
2070
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2071
0
                           ellipsoidAuthName, ellipsoidCode);
2072
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2073
0
        return {};
2074
0
    }
2075
2076
0
    std::vector<std::string> sqlStatements;
2077
2078
    // Find or insert celestial body
2079
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2080
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2081
0
    constexpr double tolerance = 0.005;
2082
0
    std::string bodyAuthName;
2083
0
    std::string bodyCode;
2084
0
    auto res = run("SELECT auth_name, code, "
2085
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2086
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2087
0
                   {semiMajorAxisMetre, tolerance});
2088
0
    if (!res.empty()) {
2089
0
        const auto &row = res.front();
2090
0
        bodyAuthName = row[0];
2091
0
        bodyCode = row[1];
2092
0
    } else {
2093
0
        bodyAuthName = authName;
2094
0
        bodyCode = "BODY_" + code;
2095
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2096
0
        const auto sql = formatStatement(
2097
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2098
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2099
0
            semiMajorAxisMetre);
2100
0
        appendSql(sqlStatements, sql);
2101
0
    }
2102
2103
    // Insert new record in ellipsoid table
2104
0
    std::string uomAuthName;
2105
0
    std::string uomCode;
2106
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2107
0
                     sqlStatements);
2108
0
    std::string invFlattening("NULL");
2109
0
    std::string semiMinorAxis("NULL");
2110
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2111
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2112
0
    } else {
2113
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2114
0
    }
2115
2116
0
    const auto sql = formatStatement(
2117
0
        "INSERT INTO ellipsoid VALUES("
2118
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2119
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2120
0
        "", // description
2121
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2122
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2123
0
        semiMinorAxis.c_str());
2124
0
    appendSql(sqlStatements, sql);
2125
2126
0
    return sqlStatements;
2127
0
}
2128
2129
// ---------------------------------------------------------------------------
2130
2131
0
static std::string anchorEpochToStr(double val) {
2132
0
    constexpr int BUF_SIZE = 16;
2133
0
    char szBuffer[BUF_SIZE];
2134
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2135
0
    return szBuffer;
2136
0
}
2137
2138
// ---------------------------------------------------------------------------
2139
2140
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2141
    const datum::GeodeticReferenceFrameNNPtr &datum,
2142
    const std::string &authName, const std::string &code, bool numericCode,
2143
0
    const std::vector<std::string> &allowedAuthorities) {
2144
2145
0
    const auto self = NN_NO_CHECK(self_.lock());
2146
2147
    // Check if the object is already known under that code
2148
0
    std::string datumAuthName;
2149
0
    std::string datumCode;
2150
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2151
0
                           datumAuthName, datumCode);
2152
0
    if (datumAuthName == authName && datumCode == code) {
2153
0
        return {};
2154
0
    }
2155
2156
0
    std::vector<std::string> sqlStatements;
2157
2158
    // Find or insert ellipsoid
2159
0
    std::string ellipsoidAuthName;
2160
0
    std::string ellipsoidCode;
2161
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2162
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2163
0
                           ellipsoidAuthName, ellipsoidCode);
2164
0
    if (ellipsoidAuthName.empty()) {
2165
0
        ellipsoidAuthName = authName;
2166
0
        if (numericCode) {
2167
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2168
0
                                                  ellipsoidAuthName, true);
2169
0
        } else {
2170
0
            ellipsoidCode = "ELLPS_" + code;
2171
0
        }
2172
0
        sqlStatements = self->getInsertStatementsFor(
2173
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2174
0
            allowedAuthorities);
2175
0
    }
2176
2177
    // Find or insert prime meridian
2178
0
    std::string pmAuthName;
2179
0
    std::string pmCode;
2180
0
    const auto &pmOfDatum = datum->primeMeridian();
2181
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2182
0
                           pmAuthName, pmCode);
2183
0
    if (pmAuthName.empty()) {
2184
0
        pmAuthName = authName;
2185
0
        if (numericCode) {
2186
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2187
0
        } else {
2188
0
            pmCode = "PM_" + code;
2189
0
        }
2190
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2191
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2192
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2193
0
                             sqlStatementsTmp.end());
2194
0
    }
2195
2196
    // Insert new record in geodetic_datum table
2197
0
    std::string publicationDate("NULL");
2198
0
    if (datum->publicationDate().has_value()) {
2199
0
        publicationDate = '\'';
2200
0
        publicationDate +=
2201
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2202
0
        publicationDate += '\'';
2203
0
    }
2204
0
    std::string frameReferenceEpoch("NULL");
2205
0
    const auto dynamicDatum =
2206
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2207
0
    if (dynamicDatum) {
2208
0
        frameReferenceEpoch =
2209
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2210
0
    }
2211
0
    const std::string anchor(*(datum->anchorDefinition()));
2212
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2213
0
    const auto sql = formatStatement(
2214
0
        "INSERT INTO geodetic_datum VALUES("
2215
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2216
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2217
0
        "", // description
2218
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2219
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
2220
0
        anchor.empty() ? nullptr : anchor.c_str(),
2221
0
        anchorEpoch.has_value()
2222
0
            ? anchorEpochToStr(
2223
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2224
0
                  .c_str()
2225
0
            : "NULL");
2226
0
    appendSql(sqlStatements, sql);
2227
2228
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2229
0
                           allowedAuthorities, sqlStatements);
2230
2231
0
    return sqlStatements;
2232
0
}
2233
2234
// ---------------------------------------------------------------------------
2235
2236
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2237
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2238
    const std::string &code, bool numericCode,
2239
0
    const std::vector<std::string> &allowedAuthorities) {
2240
0
    const auto self = NN_NO_CHECK(self_.lock());
2241
2242
    // Check if the object is already known under that code
2243
0
    std::string datumAuthName;
2244
0
    std::string datumCode;
2245
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2246
0
                           datumAuthName, datumCode);
2247
0
    if (datumAuthName == authName && datumCode == code) {
2248
0
        return {};
2249
0
    }
2250
2251
0
    std::vector<std::string> sqlStatements;
2252
2253
0
    const auto &members = ensemble->datums();
2254
0
    assert(!members.empty());
2255
2256
0
    int counter = 1;
2257
0
    std::vector<std::pair<std::string, std::string>> membersId;
2258
0
    for (const auto &member : members) {
2259
0
        std::string memberAuthName;
2260
0
        std::string memberCode;
2261
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2262
0
                               memberAuthName, memberCode);
2263
0
        if (memberAuthName.empty()) {
2264
0
            memberAuthName = authName;
2265
0
            if (numericCode) {
2266
0
                memberCode =
2267
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2268
0
            } else {
2269
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2270
0
            }
2271
0
            const auto sqlStatementsTmp =
2272
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2273
0
                                             numericCode, allowedAuthorities);
2274
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2275
0
                                 sqlStatementsTmp.end());
2276
0
        }
2277
2278
0
        membersId.emplace_back(
2279
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2280
2281
0
        ++counter;
2282
0
    }
2283
2284
0
    const bool isGeodetic =
2285
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2286
0
            members.front()) != nullptr;
2287
2288
    // Insert new record in geodetic_datum/vertical_datum table
2289
0
    const double accuracy =
2290
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2291
0
    if (isGeodetic) {
2292
0
        const auto firstDatum =
2293
0
            AuthorityFactory::create(self, membersId.front().first)
2294
0
                ->createGeodeticDatum(membersId.front().second);
2295
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2296
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2297
0
        assert(!ellipsoidIds.empty());
2298
0
        const std::string &ellipsoidAuthName =
2299
0
            *(ellipsoidIds.front()->codeSpace());
2300
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2301
0
        const auto &pm = firstDatum->primeMeridian();
2302
0
        const auto &pmIds = pm->identifiers();
2303
0
        assert(!pmIds.empty());
2304
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2305
0
        const std::string &pmCode = pmIds.front()->code();
2306
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2307
0
        const util::optional<common::Measure> &anchorEpoch =
2308
0
            firstDatum->anchorEpoch();
2309
0
        const auto sql = formatStatement(
2310
0
            "INSERT INTO geodetic_datum VALUES("
2311
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2312
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2313
0
            "", // description
2314
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2315
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2316
0
            anchor.empty() ? nullptr : anchor.c_str(),
2317
0
            anchorEpoch.has_value()
2318
0
                ? anchorEpochToStr(
2319
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2320
0
                      .c_str()
2321
0
                : "NULL");
2322
0
        appendSql(sqlStatements, sql);
2323
0
    } else {
2324
0
        const auto firstDatum =
2325
0
            AuthorityFactory::create(self, membersId.front().first)
2326
0
                ->createVerticalDatum(membersId.front().second);
2327
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2328
0
        const util::optional<common::Measure> &anchorEpoch =
2329
0
            firstDatum->anchorEpoch();
2330
0
        const auto sql = formatStatement(
2331
0
            "INSERT INTO vertical_datum VALUES("
2332
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2333
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2334
0
            "", // description
2335
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2336
0
            anchorEpoch.has_value()
2337
0
                ? anchorEpochToStr(
2338
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2339
0
                      .c_str()
2340
0
                : "NULL");
2341
0
        appendSql(sqlStatements, sql);
2342
0
    }
2343
0
    identifyOrInsertUsages(ensemble,
2344
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2345
0
                           authName, code, allowedAuthorities, sqlStatements);
2346
2347
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2348
0
                                       : "vertical_datum_ensemble_member";
2349
0
    counter = 1;
2350
0
    for (const auto &authCodePair : membersId) {
2351
0
        const auto sql = formatStatement(
2352
0
            "INSERT INTO %s VALUES("
2353
0
            "'%q','%q','%q','%q',%d);",
2354
0
            tableName, authName.c_str(), code.c_str(),
2355
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2356
0
        appendSql(sqlStatements, sql);
2357
0
        ++counter;
2358
0
    }
2359
2360
0
    return sqlStatements;
2361
0
}
2362
2363
// ---------------------------------------------------------------------------
2364
2365
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2366
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2367
    const std::string &code, bool numericCode,
2368
0
    const std::vector<std::string> &allowedAuthorities) {
2369
2370
0
    const auto self = NN_NO_CHECK(self_.lock());
2371
2372
0
    std::vector<std::string> sqlStatements;
2373
2374
    // Find or insert datum/datum ensemble
2375
0
    std::string datumAuthName;
2376
0
    std::string datumCode;
2377
0
    const auto &ensemble = crs->datumEnsemble();
2378
0
    if (ensemble) {
2379
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2380
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2381
0
                               datumAuthName, datumCode);
2382
0
        if (datumAuthName.empty()) {
2383
0
            datumAuthName = authName;
2384
0
            if (numericCode) {
2385
0
                datumCode =
2386
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2387
0
            } else {
2388
0
                datumCode = "GEODETIC_DATUM_" + code;
2389
0
            }
2390
0
            sqlStatements = self->getInsertStatementsFor(
2391
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2392
0
                allowedAuthorities);
2393
0
        }
2394
0
    } else {
2395
0
        const auto &datum = crs->datum();
2396
0
        assert(datum);
2397
0
        const auto datumNN = NN_NO_CHECK(datum);
2398
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2399
0
                               datumAuthName, datumCode);
2400
0
        if (datumAuthName.empty()) {
2401
0
            datumAuthName = authName;
2402
0
            if (numericCode) {
2403
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2404
0
            } else {
2405
0
                datumCode = "GEODETIC_DATUM_" + code;
2406
0
            }
2407
0
            sqlStatements =
2408
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2409
0
                                             numericCode, allowedAuthorities);
2410
0
        }
2411
0
    }
2412
2413
    // Find or insert coordinate system
2414
0
    const auto &coordinateSystem = crs->coordinateSystem();
2415
0
    std::string csAuthName;
2416
0
    std::string csCode;
2417
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2418
0
                     csAuthName, csCode, sqlStatements);
2419
2420
0
    const char *type = GEOG_2D;
2421
0
    if (coordinateSystem->axisList().size() == 3) {
2422
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2423
0
            type = GEOG_3D;
2424
0
        } else {
2425
0
            type = GEOCENTRIC;
2426
0
        }
2427
0
    }
2428
2429
    // Insert new record in geodetic_crs table
2430
0
    const auto sql =
2431
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2432
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2433
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2434
0
                        "", // description
2435
0
                        type, csAuthName.c_str(), csCode.c_str(),
2436
0
                        datumAuthName.c_str(), datumCode.c_str());
2437
0
    appendSql(sqlStatements, sql);
2438
2439
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2440
0
                           allowedAuthorities, sqlStatements);
2441
0
    return sqlStatements;
2442
0
}
2443
2444
// ---------------------------------------------------------------------------
2445
2446
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2447
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2448
    const std::string &code, bool numericCode,
2449
0
    const std::vector<std::string> &allowedAuthorities) {
2450
2451
0
    const auto self = NN_NO_CHECK(self_.lock());
2452
2453
0
    std::vector<std::string> sqlStatements;
2454
2455
    // Find or insert base geodetic CRS
2456
0
    const auto &baseCRS = crs->baseCRS();
2457
0
    std::string geodAuthName;
2458
0
    std::string geodCode;
2459
2460
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2461
0
    allowedAuthoritiesTmp.emplace_back(authName);
2462
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2463
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2464
0
        const auto candidates = baseCRS->identify(factory);
2465
0
        for (const auto &candidate : candidates) {
2466
0
            if (candidate.second == 100) {
2467
0
                const auto &ids = candidate.first->identifiers();
2468
0
                if (!ids.empty()) {
2469
0
                    const auto &id = ids.front();
2470
0
                    geodAuthName = *(id->codeSpace());
2471
0
                    geodCode = id->code();
2472
0
                    break;
2473
0
                }
2474
0
            }
2475
0
            if (!geodAuthName.empty()) {
2476
0
                break;
2477
0
            }
2478
0
        }
2479
0
    }
2480
0
    if (geodAuthName.empty()) {
2481
0
        geodAuthName = authName;
2482
0
        geodCode = "GEODETIC_CRS_" + code;
2483
0
        sqlStatements = self->getInsertStatementsFor(
2484
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2485
0
    }
2486
2487
    // Insert new record in conversion table
2488
0
    const auto &conversion = crs->derivingConversionRef();
2489
0
    std::string convAuthName(authName);
2490
0
    std::string convCode("CONVERSION_" + code);
2491
0
    if (numericCode) {
2492
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2493
0
    }
2494
0
    {
2495
0
        const auto &method = conversion->method();
2496
0
        const auto &methodIds = method->identifiers();
2497
0
        std::string methodAuthName;
2498
0
        std::string methodCode;
2499
0
        const operation::MethodMapping *methodMapping = nullptr;
2500
0
        if (methodIds.empty()) {
2501
0
            const int epsgCode = method->getEPSGCode();
2502
0
            if (epsgCode > 0) {
2503
0
                methodAuthName = metadata::Identifier::EPSG;
2504
0
                methodCode = toString(epsgCode);
2505
0
            } else {
2506
0
                const auto &methodName = method->nameStr();
2507
0
                size_t nProjectionMethodMappings = 0;
2508
0
                const auto projectionMethodMappings =
2509
0
                    operation::getProjectionMethodMappings(
2510
0
                        nProjectionMethodMappings);
2511
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2512
0
                    const auto &mapping = projectionMethodMappings[i];
2513
0
                    if (metadata::Identifier::isEquivalentName(
2514
0
                            mapping.wkt2_name, methodName.c_str())) {
2515
0
                        methodMapping = &mapping;
2516
0
                    }
2517
0
                }
2518
0
                if (methodMapping == nullptr ||
2519
0
                    methodMapping->proj_name_main == nullptr) {
2520
0
                    throw FactoryException("Cannot insert projection with "
2521
0
                                           "method without identifier");
2522
0
                }
2523
0
                methodAuthName = "PROJ";
2524
0
                methodCode = methodMapping->proj_name_main;
2525
0
                if (methodMapping->proj_name_aux) {
2526
0
                    methodCode += ' ';
2527
0
                    methodCode += methodMapping->proj_name_aux;
2528
0
                }
2529
0
            }
2530
0
        } else {
2531
0
            const auto &methodId = methodIds.front();
2532
0
            methodAuthName = *(methodId->codeSpace());
2533
0
            methodCode = methodId->code();
2534
0
        }
2535
2536
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2537
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2538
0
                                   convAuthName.c_str(), convCode.c_str(),
2539
0
                                   conversion->nameStr().c_str(),
2540
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2541
0
                                   method->nameStr().c_str());
2542
0
        const auto &srcValues = conversion->parameterValues();
2543
0
        if (srcValues.size() > N_MAX_PARAMS) {
2544
0
            throw FactoryException("Cannot insert projection with more than " +
2545
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2546
0
                                   " parameters");
2547
0
        }
2548
2549
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2550
0
        if (methodMapping == nullptr) {
2551
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2552
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2553
0
            } else {
2554
0
                methodMapping =
2555
0
                    operation::getMapping(method->nameStr().c_str());
2556
0
            }
2557
0
        }
2558
0
        if (methodMapping != nullptr) {
2559
            // Re-order projection parameters in their reference order
2560
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2561
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2562
0
                    auto opParamValue = dynamic_cast<
2563
0
                        const operation::OperationParameterValue *>(
2564
0
                        srcValues[i].get());
2565
0
                    if (!opParamValue) {
2566
0
                        throw FactoryException("Cannot insert projection with "
2567
0
                                               "non-OperationParameterValue");
2568
0
                    }
2569
0
                    if (methodMapping->params[j]->wkt2_name &&
2570
0
                        opParamValue->parameter()->nameStr() ==
2571
0
                            methodMapping->params[j]->wkt2_name) {
2572
0
                        values.emplace_back(srcValues[i]);
2573
0
                    }
2574
0
                }
2575
0
            }
2576
0
        }
2577
0
        if (values.size() != srcValues.size()) {
2578
0
            values = srcValues;
2579
0
        }
2580
2581
0
        for (const auto &genOpParamvalue : values) {
2582
0
            auto opParamValue =
2583
0
                dynamic_cast<const operation::OperationParameterValue *>(
2584
0
                    genOpParamvalue.get());
2585
0
            if (!opParamValue) {
2586
0
                throw FactoryException("Cannot insert projection with "
2587
0
                                       "non-OperationParameterValue");
2588
0
            }
2589
0
            const auto &param = opParamValue->parameter();
2590
0
            const auto &paramIds = param->identifiers();
2591
0
            std::string paramAuthName;
2592
0
            std::string paramCode;
2593
0
            if (paramIds.empty()) {
2594
0
                const int paramEPSGCode = param->getEPSGCode();
2595
0
                if (paramEPSGCode == 0) {
2596
0
                    throw FactoryException(
2597
0
                        "Cannot insert projection with method parameter "
2598
0
                        "without identifier");
2599
0
                }
2600
0
                paramAuthName = metadata::Identifier::EPSG;
2601
0
                paramCode = toString(paramEPSGCode);
2602
0
            } else {
2603
0
                const auto &paramId = paramIds.front();
2604
0
                paramAuthName = *(paramId->codeSpace());
2605
0
                paramCode = paramId->code();
2606
0
            }
2607
0
            const auto &value = opParamValue->parameterValue()->value();
2608
0
            const auto &unit = value.unit();
2609
0
            std::string uomAuthName;
2610
0
            std::string uomCode;
2611
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2612
0
                             sqlStatements);
2613
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2614
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2615
0
                                   param->nameStr().c_str(), value.value(),
2616
0
                                   uomAuthName.c_str(), uomCode.c_str());
2617
0
        }
2618
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2619
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2620
0
        }
2621
0
        sql += ",0);";
2622
0
        appendSql(sqlStatements, sql);
2623
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2624
0
                               allowedAuthorities, sqlStatements);
2625
0
    }
2626
2627
    // Find or insert coordinate system
2628
0
    const auto &coordinateSystem = crs->coordinateSystem();
2629
0
    std::string csAuthName;
2630
0
    std::string csCode;
2631
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2632
0
                     csAuthName, csCode, sqlStatements);
2633
2634
    // Insert new record in projected_crs table
2635
0
    const auto sql = formatStatement(
2636
0
        "INSERT INTO projected_crs VALUES("
2637
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2638
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2639
0
        "", // description
2640
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2641
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2642
0
    appendSql(sqlStatements, sql);
2643
2644
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2645
0
                           allowedAuthorities, sqlStatements);
2646
2647
0
    return sqlStatements;
2648
0
}
2649
2650
// ---------------------------------------------------------------------------
2651
2652
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2653
    const datum::VerticalReferenceFrameNNPtr &datum,
2654
    const std::string &authName, const std::string &code,
2655
    bool /* numericCode */,
2656
0
    const std::vector<std::string> &allowedAuthorities) {
2657
2658
0
    const auto self = NN_NO_CHECK(self_.lock());
2659
2660
0
    std::vector<std::string> sqlStatements;
2661
2662
    // Check if the object is already known under that code
2663
0
    std::string datumAuthName;
2664
0
    std::string datumCode;
2665
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2666
0
                           datumAuthName, datumCode);
2667
0
    if (datumAuthName == authName && datumCode == code) {
2668
0
        return {};
2669
0
    }
2670
2671
    // Insert new record in vertical_datum table
2672
0
    std::string publicationDate("NULL");
2673
0
    if (datum->publicationDate().has_value()) {
2674
0
        publicationDate = '\'';
2675
0
        publicationDate +=
2676
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2677
0
        publicationDate += '\'';
2678
0
    }
2679
0
    std::string frameReferenceEpoch("NULL");
2680
0
    const auto dynamicDatum =
2681
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2682
0
    if (dynamicDatum) {
2683
0
        frameReferenceEpoch =
2684
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2685
0
    }
2686
0
    const std::string anchor(*(datum->anchorDefinition()));
2687
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2688
0
    const auto sql = formatStatement(
2689
0
        "INSERT INTO vertical_datum VALUES("
2690
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2691
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2692
0
        "", // description
2693
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2694
0
        anchor.empty() ? nullptr : anchor.c_str(),
2695
0
        anchorEpoch.has_value()
2696
0
            ? anchorEpochToStr(
2697
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2698
0
                  .c_str()
2699
0
            : "NULL");
2700
0
    appendSql(sqlStatements, sql);
2701
2702
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2703
0
                           allowedAuthorities, sqlStatements);
2704
2705
0
    return sqlStatements;
2706
0
}
2707
2708
// ---------------------------------------------------------------------------
2709
2710
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2711
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2712
    const std::string &code, bool numericCode,
2713
0
    const std::vector<std::string> &allowedAuthorities) {
2714
2715
0
    const auto self = NN_NO_CHECK(self_.lock());
2716
2717
0
    std::vector<std::string> sqlStatements;
2718
2719
    // Find or insert datum/datum ensemble
2720
0
    std::string datumAuthName;
2721
0
    std::string datumCode;
2722
0
    const auto &ensemble = crs->datumEnsemble();
2723
0
    if (ensemble) {
2724
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2725
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2726
0
                               datumAuthName, datumCode);
2727
0
        if (datumAuthName.empty()) {
2728
0
            datumAuthName = authName;
2729
0
            if (numericCode) {
2730
0
                datumCode =
2731
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2732
0
            } else {
2733
0
                datumCode = "VERTICAL_DATUM_" + code;
2734
0
            }
2735
0
            sqlStatements = self->getInsertStatementsFor(
2736
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2737
0
                allowedAuthorities);
2738
0
        }
2739
0
    } else {
2740
0
        const auto &datum = crs->datum();
2741
0
        assert(datum);
2742
0
        const auto datumNN = NN_NO_CHECK(datum);
2743
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2744
0
                               datumAuthName, datumCode);
2745
0
        if (datumAuthName.empty()) {
2746
0
            datumAuthName = authName;
2747
0
            if (numericCode) {
2748
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2749
0
            } else {
2750
0
                datumCode = "VERTICAL_DATUM_" + code;
2751
0
            }
2752
0
            sqlStatements =
2753
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2754
0
                                             numericCode, allowedAuthorities);
2755
0
        }
2756
0
    }
2757
2758
    // Find or insert coordinate system
2759
0
    const auto &coordinateSystem = crs->coordinateSystem();
2760
0
    std::string csAuthName;
2761
0
    std::string csCode;
2762
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2763
0
                     csAuthName, csCode, sqlStatements);
2764
2765
    // Insert new record in vertical_crs table
2766
0
    const auto sql =
2767
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2768
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2769
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2770
0
                        "", // description
2771
0
                        csAuthName.c_str(), csCode.c_str(),
2772
0
                        datumAuthName.c_str(), datumCode.c_str());
2773
0
    appendSql(sqlStatements, sql);
2774
2775
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2776
0
                           allowedAuthorities, sqlStatements);
2777
2778
0
    return sqlStatements;
2779
0
}
2780
2781
// ---------------------------------------------------------------------------
2782
2783
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2784
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2785
    const std::string &code, bool numericCode,
2786
0
    const std::vector<std::string> &allowedAuthorities) {
2787
2788
0
    const auto self = NN_NO_CHECK(self_.lock());
2789
2790
0
    std::vector<std::string> sqlStatements;
2791
2792
0
    int counter = 1;
2793
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2794
0
    const auto &components = crs->componentReferenceSystems();
2795
0
    if (components.size() != 2) {
2796
0
        throw FactoryException(
2797
0
            "Cannot insert compound CRS with number of components != 2");
2798
0
    }
2799
2800
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2801
0
    allowedAuthoritiesTmp.emplace_back(authName);
2802
2803
0
    for (const auto &component : components) {
2804
0
        std::string compAuthName;
2805
0
        std::string compCode;
2806
2807
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2808
0
            const auto factory =
2809
0
                AuthorityFactory::create(self, allowedAuthority);
2810
0
            const auto candidates = component->identify(factory);
2811
0
            for (const auto &candidate : candidates) {
2812
0
                if (candidate.second == 100) {
2813
0
                    const auto &ids = candidate.first->identifiers();
2814
0
                    if (!ids.empty()) {
2815
0
                        const auto &id = ids.front();
2816
0
                        compAuthName = *(id->codeSpace());
2817
0
                        compCode = id->code();
2818
0
                        break;
2819
0
                    }
2820
0
                }
2821
0
                if (!compAuthName.empty()) {
2822
0
                    break;
2823
0
                }
2824
0
            }
2825
0
        }
2826
2827
0
        if (compAuthName.empty()) {
2828
0
            compAuthName = authName;
2829
0
            if (numericCode) {
2830
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2831
0
            } else {
2832
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2833
0
            }
2834
0
            const auto sqlStatementsTmp =
2835
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2836
0
                                             numericCode, allowedAuthorities);
2837
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2838
0
                                 sqlStatementsTmp.end());
2839
0
        }
2840
2841
0
        componentsId.emplace_back(
2842
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2843
2844
0
        ++counter;
2845
0
    }
2846
2847
    // Insert new record in compound_crs table
2848
0
    const auto sql = formatStatement(
2849
0
        "INSERT INTO compound_crs VALUES("
2850
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2851
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2852
0
        "", // description
2853
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2854
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2855
0
    appendSql(sqlStatements, sql);
2856
2857
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2858
0
                           allowedAuthorities, sqlStatements);
2859
2860
0
    return sqlStatements;
2861
0
}
2862
2863
//! @endcond
2864
2865
// ---------------------------------------------------------------------------
2866
2867
//! @cond Doxygen_Suppress
2868
70.6k
DatabaseContext::~DatabaseContext() {
2869
70.6k
    try {
2870
70.6k
        stopInsertStatementsSession();
2871
70.6k
    } catch (const std::exception &) {
2872
0
    }
2873
70.6k
}
2874
//! @endcond
2875
2876
// ---------------------------------------------------------------------------
2877
2878
70.6k
DatabaseContext::DatabaseContext() : d(internal::make_unique<Private>()) {}
2879
2880
// ---------------------------------------------------------------------------
2881
2882
/** \brief Instantiate a database context.
2883
 *
2884
 * This database context should be used only by one thread at a time.
2885
 *
2886
 * @param databasePath Path and filename of the database. Might be empty
2887
 * string for the default rules to locate the default proj.db
2888
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2889
 * Might be empty.
2890
 * Starting with PROJ 8.1, if this parameter is an empty array,
2891
 * the PROJ_AUX_DB environment variable will be used, if set.
2892
 * It must contain one or several paths. If several paths are
2893
 * provided, they must be separated by the colon (:) character on Unix, and
2894
 * on Windows, by the semi-colon (;) character.
2895
 * @param ctx Context used for file search.
2896
 * @throw FactoryException
2897
 */
2898
DatabaseContextNNPtr
2899
DatabaseContext::create(const std::string &databasePath,
2900
                        const std::vector<std::string> &auxiliaryDatabasePaths,
2901
70.6k
                        PJ_CONTEXT *ctx) {
2902
70.6k
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
2903
70.6k
    auto dbCtxPrivate = dbCtx->getPrivate();
2904
70.6k
    dbCtxPrivate->open(databasePath, ctx);
2905
70.6k
    auto auxDbs(auxiliaryDatabasePaths);
2906
70.6k
    if (auxDbs.empty()) {
2907
0
        const char *auxDbStr = getenv("PROJ_AUX_DB");
2908
0
        if (auxDbStr) {
2909
#ifdef _WIN32
2910
            const char *delim = ";";
2911
#else
2912
0
            const char *delim = ":";
2913
0
#endif
2914
0
            auxDbs = split(auxDbStr, delim);
2915
0
        }
2916
0
    }
2917
70.6k
    if (!auxDbs.empty()) {
2918
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
2919
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
2920
0
    }
2921
70.6k
    dbCtxPrivate->self_ = dbCtx.as_nullable();
2922
70.6k
    return dbCtx;
2923
70.6k
}
2924
2925
// ---------------------------------------------------------------------------
2926
2927
/** \brief Return the list of authorities used in the database.
2928
 */
2929
0
std::set<std::string> DatabaseContext::getAuthorities() const {
2930
0
    auto res = d->run("SELECT auth_name FROM authority_list");
2931
0
    std::set<std::string> list;
2932
0
    for (const auto &row : res) {
2933
0
        list.insert(row[0]);
2934
0
    }
2935
0
    return list;
2936
0
}
2937
2938
// ---------------------------------------------------------------------------
2939
2940
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
2941
 * CREATE VIEW) needed to initialize a new database.
2942
 */
2943
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
2944
0
    return d->getDatabaseStructure();
2945
0
}
2946
2947
// ---------------------------------------------------------------------------
2948
2949
/** \brief Return the path to the database.
2950
 */
2951
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
2952
2953
// ---------------------------------------------------------------------------
2954
2955
/** \brief Return a metadata item.
2956
 *
2957
 * Value remains valid while this is alive and to the next call to getMetadata
2958
 */
2959
0
const char *DatabaseContext::getMetadata(const char *key) const {
2960
0
    auto res =
2961
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
2962
0
    if (res.empty()) {
2963
0
        return nullptr;
2964
0
    }
2965
0
    d->lastMetadataValue_ = res.front()[0];
2966
0
    return d->lastMetadataValue_.c_str();
2967
0
}
2968
2969
// ---------------------------------------------------------------------------
2970
2971
/** \brief Starts a session for getInsertStatementsFor()
2972
 *
2973
 * Starts a new session for one or several calls to getInsertStatementsFor().
2974
 * An insertion session guarantees that the inserted objects will not create
2975
 * conflicting intermediate objects.
2976
 *
2977
 * The session must be stopped with stopInsertStatementsSession().
2978
 *
2979
 * Only one session may be active at a time for a given database context.
2980
 *
2981
 * @throw FactoryException
2982
 * @since 8.1
2983
 */
2984
0
void DatabaseContext::startInsertStatementsSession() {
2985
0
    if (d->memoryDbHandle_) {
2986
0
        throw FactoryException(
2987
0
            "startInsertStatementsSession() cannot be invoked until "
2988
0
            "stopInsertStatementsSession() is.");
2989
0
    }
2990
2991
0
    d->memoryDbForInsertPath_.clear();
2992
0
    const auto sqlStatements = getDatabaseStructure();
2993
2994
    // Create a in-memory temporary sqlite3 database
2995
0
    std::ostringstream buffer;
2996
0
    buffer << "file:temp_db_for_insert_statements_";
2997
0
    buffer << this;
2998
0
    buffer << ".db?mode=memory&cache=shared";
2999
0
    d->memoryDbForInsertPath_ = buffer.str();
3000
0
    sqlite3 *memoryDbHandle = nullptr;
3001
0
    sqlite3_open_v2(
3002
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3003
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3004
0
    if (memoryDbHandle == nullptr) {
3005
0
        throw FactoryException("Cannot create in-memory database");
3006
0
    }
3007
0
    d->memoryDbHandle_ =
3008
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3009
3010
    // Fill the structure of this database
3011
0
    for (const auto &sql : sqlStatements) {
3012
0
        char *errmsg = nullptr;
3013
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3014
0
                         nullptr, &errmsg) != SQLITE_OK) {
3015
0
            const auto sErrMsg =
3016
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3017
0
            sqlite3_free(errmsg);
3018
0
            throw FactoryException(sErrMsg);
3019
0
        }
3020
0
        sqlite3_free(errmsg);
3021
0
    }
3022
3023
    // Attach this database to the current one(s)
3024
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3025
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3026
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3027
0
}
3028
3029
// ---------------------------------------------------------------------------
3030
3031
/** \brief Suggests a database code for the passed object.
3032
 *
3033
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3034
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3035
 *
3036
 * @param object Object for which to suggest a code.
3037
 * @param authName Authority name into which the object will be inserted.
3038
 * @param numericCode Whether the code should be numeric, or derived from the
3039
 * object name.
3040
 * @return the suggested code, that is guaranteed to not conflict with an
3041
 * existing one.
3042
 *
3043
 * @throw FactoryException
3044
 * @since 8.1
3045
 */
3046
std::string
3047
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3048
                                 const std::string &authName,
3049
0
                                 bool numericCode) {
3050
0
    const char *tableName = "";
3051
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3052
0
        tableName = "prime_meridian";
3053
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3054
0
        tableName = "ellipsoid";
3055
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3056
0
                   object.get())) {
3057
0
        tableName = "geodetic_datum";
3058
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3059
0
                   object.get())) {
3060
0
        tableName = "vertical_datum";
3061
0
    } else if (const auto ensemble =
3062
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3063
0
        const auto &datums = ensemble->datums();
3064
0
        if (!datums.empty() &&
3065
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3066
0
                datums[0].get())) {
3067
0
            tableName = "geodetic_datum";
3068
0
        } else {
3069
0
            tableName = "vertical_datum";
3070
0
        }
3071
0
    } else if (const auto boundCRS =
3072
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3073
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3074
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3075
0
        tableName = "crs_view";
3076
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3077
0
        tableName = "conversion";
3078
0
    } else {
3079
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3080
0
    }
3081
3082
0
    if (numericCode) {
3083
0
        std::string sql("SELECT MAX(code) FROM ");
3084
0
        sql += tableName;
3085
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3086
0
               "AND upper(code) = lower(code)";
3087
0
        const auto res = d->run(sql, {authName});
3088
0
        if (res.empty()) {
3089
0
            return "1";
3090
0
        }
3091
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3092
0
    }
3093
3094
0
    std::string code;
3095
0
    code.reserve(object->nameStr().size());
3096
0
    bool insertUnderscore = false;
3097
0
    for (const auto ch : toupper(object->nameStr())) {
3098
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3099
0
            if (insertUnderscore && code.back() != '_')
3100
0
                code += '_';
3101
0
            code += ch;
3102
0
            insertUnderscore = false;
3103
0
        } else {
3104
0
            insertUnderscore = true;
3105
0
        }
3106
0
    }
3107
0
    return d->findFreeCode(tableName, authName, code);
3108
0
}
3109
3110
// ---------------------------------------------------------------------------
3111
3112
/** \brief Returns SQL statements needed to insert the passed object into the
3113
 * database.
3114
 *
3115
 * startInsertStatementsSession() must have been called previously.
3116
 *
3117
 * @param object The object to insert into the database. Currently only
3118
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3119
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3120
 * @param authName Authority name into which the object will be inserted.
3121
 * @param code Code with which the object will be inserted.
3122
 * @param numericCode Whether intermediate objects that can be created should
3123
 *                    use numeric codes (true), or may be alphanumeric (false)
3124
 * @param allowedAuthorities Authorities to which intermediate objects are
3125
 *                           allowed to refer to. authName will be implicitly
3126
 *                           added to it. Note that unit, coordinate
3127
 *                           systems, projection methods and parameters will in
3128
 *                           any case be allowed to refer to EPSG.
3129
 * @throw FactoryException
3130
 * @since 8.1
3131
 */
3132
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3133
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3134
    const std::string &code, bool numericCode,
3135
0
    const std::vector<std::string> &allowedAuthorities) {
3136
0
    if (d->memoryDbHandle_ == nullptr) {
3137
0
        throw FactoryException(
3138
0
            "startInsertStatementsSession() should be invoked first");
3139
0
    }
3140
3141
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3142
0
    if (crs) {
3143
        // Check if the object is already known under that code
3144
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3145
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3146
0
        allowedAuthoritiesTmp.emplace_back(authName);
3147
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3148
0
            const auto factory =
3149
0
                AuthorityFactory::create(self, allowedAuthority);
3150
0
            const auto candidates = crs->identify(factory);
3151
0
            for (const auto &candidate : candidates) {
3152
0
                if (candidate.second == 100) {
3153
0
                    const auto &ids = candidate.first->identifiers();
3154
0
                    for (const auto &id : ids) {
3155
0
                        if (*(id->codeSpace()) == authName &&
3156
0
                            id->code() == code) {
3157
0
                            return {};
3158
0
                        }
3159
0
                    }
3160
0
                }
3161
0
            }
3162
0
        }
3163
0
    }
3164
3165
0
    if (const auto pm =
3166
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3167
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3168
0
                                         numericCode, allowedAuthorities);
3169
0
    }
3170
3171
0
    else if (const auto ellipsoid =
3172
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3173
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3174
0
                                         numericCode, allowedAuthorities);
3175
0
    }
3176
3177
0
    else if (const auto geodeticDatum =
3178
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3179
0
                     object)) {
3180
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3181
0
                                         code, numericCode, allowedAuthorities);
3182
0
    }
3183
3184
0
    else if (const auto ensemble =
3185
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3186
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3187
0
                                         numericCode, allowedAuthorities);
3188
0
    }
3189
3190
0
    else if (const auto geodCRS =
3191
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3192
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3193
0
                                         numericCode, allowedAuthorities);
3194
0
    }
3195
3196
0
    else if (const auto projCRS =
3197
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3198
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3199
0
                                         numericCode, allowedAuthorities);
3200
0
    }
3201
3202
0
    else if (const auto verticalDatum =
3203
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3204
0
                     object)) {
3205
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3206
0
                                         code, numericCode, allowedAuthorities);
3207
0
    }
3208
3209
0
    else if (const auto vertCRS =
3210
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3211
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3212
0
                                         numericCode, allowedAuthorities);
3213
0
    }
3214
3215
0
    else if (const auto compoundCRS =
3216
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3217
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3218
0
                                         code, numericCode, allowedAuthorities);
3219
0
    }
3220
3221
0
    else if (const auto boundCRS =
3222
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3223
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3224
0
                                      numericCode, allowedAuthorities);
3225
0
    }
3226
3227
0
    else {
3228
0
        throw FactoryException(
3229
0
            "getInsertStatementsFor(): unhandled type of object");
3230
0
    }
3231
0
}
3232
3233
// ---------------------------------------------------------------------------
3234
3235
/** \brief Stops an insertion session started with
3236
 * startInsertStatementsSession()
3237
 *
3238
 * @since 8.1
3239
 */
3240
70.6k
void DatabaseContext::stopInsertStatementsSession() {
3241
70.6k
    if (d->memoryDbHandle_) {
3242
0
        d->clearCaches();
3243
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3244
0
        d->memoryDbHandle_.reset();
3245
0
        d->memoryDbForInsertPath_.clear();
3246
0
    }
3247
70.6k
}
3248
3249
// ---------------------------------------------------------------------------
3250
3251
//! @cond Doxygen_Suppress
3252
3253
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3254
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3255
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3256
0
    return ctxt;
3257
0
}
3258
3259
// ---------------------------------------------------------------------------
3260
3261
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3262
3263
// ---------------------------------------------------------------------------
3264
3265
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3266
                                             std::string &projFilename,
3267
                                             std::string &projFormat,
3268
0
                                             bool &inverse) const {
3269
0
    auto res = d->run(
3270
0
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3271
0
        "grid_alternatives WHERE original_grid_name = ? AND "
3272
0
        "proj_grid_name <> ''",
3273
0
        {officialName});
3274
0
    if (res.empty()) {
3275
0
        return false;
3276
0
    }
3277
0
    const auto &row = res.front();
3278
0
    projFilename = row[0];
3279
0
    projFormat = row[1];
3280
0
    inverse = row[2] == "1";
3281
0
    return true;
3282
0
}
3283
3284
// ---------------------------------------------------------------------------
3285
3286
bool DatabaseContext::lookForGridInfo(
3287
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3288
    std::string &fullFilename, std::string &packageName, std::string &url,
3289
0
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3290
0
    Private::GridInfoCache info;
3291
3292
0
    if (projFilename == "null") {
3293
        // Special case for implicit "null" grid.
3294
0
        fullFilename.clear();
3295
0
        packageName.clear();
3296
0
        url.clear();
3297
0
        directDownload = false;
3298
0
        openLicense = true;
3299
0
        gridAvailable = true;
3300
0
        return true;
3301
0
    }
3302
3303
0
    auto ctxt = d->pjCtxt();
3304
0
    if (ctxt == nullptr) {
3305
0
        ctxt = pj_get_default_ctx();
3306
0
        d->setPjCtxt(ctxt);
3307
0
    }
3308
3309
0
    std::string key(projFilename);
3310
0
    key += proj_context_is_network_enabled(ctxt) ? "true" : "false";
3311
0
    key += considerKnownGridsAsAvailable ? "true" : "false";
3312
0
    if (d->getGridInfoFromCache(key, info)) {
3313
0
        fullFilename = info.fullFilename;
3314
0
        packageName = info.packageName;
3315
0
        url = info.url;
3316
0
        directDownload = info.directDownload;
3317
0
        openLicense = info.openLicense;
3318
0
        gridAvailable = info.gridAvailable;
3319
0
        return info.found;
3320
0
    }
3321
3322
0
    fullFilename.clear();
3323
0
    packageName.clear();
3324
0
    url.clear();
3325
0
    openLicense = false;
3326
0
    directDownload = false;
3327
3328
0
    fullFilename.resize(2048);
3329
0
    int errno_before = proj_context_errno(ctxt);
3330
0
    gridAvailable = NS_PROJ::FileManager::open_resource_file(
3331
0
                        ctxt, projFilename.c_str(), &fullFilename[0],
3332
0
                        fullFilename.size() - 1) != nullptr;
3333
0
    proj_context_errno_set(ctxt, errno_before);
3334
0
    fullFilename.resize(strlen(fullFilename.c_str()));
3335
3336
0
    auto res =
3337
0
        d->run("SELECT "
3338
0
               "grid_packages.package_name, "
3339
0
               "grid_alternatives.url, "
3340
0
               "grid_packages.url AS package_url, "
3341
0
               "grid_alternatives.open_license, "
3342
0
               "grid_packages.open_license AS package_open_license, "
3343
0
               "grid_alternatives.direct_download, "
3344
0
               "grid_packages.direct_download AS package_direct_download, "
3345
0
               "grid_alternatives.proj_grid_name, "
3346
0
               "grid_alternatives.old_proj_grid_name "
3347
0
               "FROM grid_alternatives "
3348
0
               "LEFT JOIN grid_packages ON "
3349
0
               "grid_alternatives.package_name = grid_packages.package_name "
3350
0
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3351
0
               {projFilename, projFilename});
3352
0
    bool ret = !res.empty();
3353
0
    if (ret) {
3354
0
        const auto &row = res.front();
3355
0
        packageName = std::move(row[0]);
3356
0
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3357
0
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3358
0
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3359
3360
0
        const auto &proj_grid_name = row[7];
3361
0
        const auto &old_proj_grid_name = row[8];
3362
0
        if (proj_grid_name != old_proj_grid_name &&
3363
0
            old_proj_grid_name == projFilename) {
3364
0
            std::string fullFilenameNewName;
3365
0
            fullFilenameNewName.resize(2048);
3366
0
            errno_before = proj_context_errno(ctxt);
3367
0
            bool gridAvailableWithNewName =
3368
0
                pj_find_file(ctxt, proj_grid_name.c_str(),
3369
0
                             &fullFilenameNewName[0],
3370
0
                             fullFilenameNewName.size() - 1) != 0;
3371
0
            proj_context_errno_set(ctxt, errno_before);
3372
0
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3373
0
            if (gridAvailableWithNewName) {
3374
0
                gridAvailable = true;
3375
0
                fullFilename = std::move(fullFilenameNewName);
3376
0
            }
3377
0
        }
3378
3379
0
        if (considerKnownGridsAsAvailable &&
3380
0
            (!packageName.empty() || (!url.empty() && openLicense))) {
3381
0
            gridAvailable = true;
3382
0
        }
3383
3384
0
        info.packageName = packageName;
3385
0
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3386
0
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3387
0
            if (endpoint.back() != '/') {
3388
0
                endpoint += '/';
3389
0
            }
3390
0
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3391
0
        }
3392
0
        info.directDownload = directDownload;
3393
0
        info.openLicense = openLicense;
3394
0
    } else {
3395
0
        if (starts_with(fullFilename, "http://") ||
3396
0
            starts_with(fullFilename, "https://")) {
3397
0
            url = fullFilename;
3398
0
            fullFilename.clear();
3399
0
        }
3400
0
    }
3401
3402
0
    info.fullFilename = fullFilename;
3403
0
    info.url = url;
3404
0
    info.gridAvailable = gridAvailable;
3405
0
    info.found = ret;
3406
0
    d->cache(key, info);
3407
0
    return ret;
3408
0
}
3409
3410
// ---------------------------------------------------------------------------
3411
3412
bool DatabaseContext::isKnownName(const std::string &name,
3413
0
                                  const std::string &tableName) const {
3414
0
    std::string sql("SELECT 1 FROM \"");
3415
0
    sql += replaceAll(tableName, "\"", "\"\"");
3416
0
    sql += "\" WHERE name = ? LIMIT 1";
3417
0
    return !d->run(sql, {name}).empty();
3418
0
}
3419
// ---------------------------------------------------------------------------
3420
3421
std::string
3422
0
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3423
0
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3424
0
                      "old_proj_grid_name = ?",
3425
0
                      {oldProjGridName});
3426
0
    if (res.empty()) {
3427
0
        return std::string();
3428
0
    }
3429
0
    return res.front()[0];
3430
0
}
3431
3432
// ---------------------------------------------------------------------------
3433
3434
0
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3435
0
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3436
0
                      "proj_grid_name = ?",
3437
0
                      {gridName});
3438
0
    if (res.empty()) {
3439
0
        return std::string();
3440
0
    }
3441
0
    return res.front()[0];
3442
0
}
3443
3444
// ---------------------------------------------------------------------------
3445
3446
// scripts/build_db_from_esri.py adds a second alias for
3447
// names that have '[' in them. See get_old_esri_name()
3448
// in scripts/build_db_from_esri.py
3449
// So if we only have two aliases detect that situation to get the official
3450
// new name
3451
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3452
0
    std::string first = l.front();
3453
0
    std::string second = *(std::next(l.begin()));
3454
0
    if (second.find('[') != std::string::npos)
3455
0
        std::swap(first, second);
3456
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3457
0
        second) {
3458
0
        return first;
3459
0
    }
3460
0
    return std::string();
3461
0
}
3462
3463
// ---------------------------------------------------------------------------
3464
3465
/** \brief Gets the alias name from an official name.
3466
 *
3467
 * @param officialName Official name. Mandatory
3468
 * @param tableName Table name/category. Mandatory.
3469
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3470
 *                  accepted as special names to add a constraint on the "type"
3471
 *                  column of the "geodetic_crs" table.
3472
 * @param source Source of the alias. Mandatory
3473
 * @return Alias name (or empty if not found).
3474
 * @throw FactoryException
3475
 */
3476
std::string
3477
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3478
                                          const std::string &tableName,
3479
0
                                          const std::string &source) const {
3480
0
    std::string sql("SELECT auth_name, code FROM \"");
3481
0
    const auto genuineTableName =
3482
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3483
0
            ? "geodetic_crs"
3484
0
            : tableName;
3485
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3486
0
    sql += "\" WHERE name = ?";
3487
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3488
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3489
0
    } else if (tableName == "geographic_3D_crs") {
3490
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3491
0
    }
3492
0
    sql += " ORDER BY deprecated";
3493
0
    auto res = d->run(sql, {officialName});
3494
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3495
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3496
    // NAD83(HARN), and that's definitely not desirable.
3497
0
    if (res.empty() &&
3498
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3499
0
        res = d->run(
3500
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3501
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3502
0
            {genuineTableName, officialName});
3503
0
        if (res.size() != 1) {
3504
0
            return std::string();
3505
0
        }
3506
0
    }
3507
0
    for (const auto &row : res) {
3508
0
        auto res2 =
3509
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3510
0
                   "auth_name = ? AND code = ? AND source = ?",
3511
0
                   {genuineTableName, row[0], row[1], source});
3512
0
        if (!res2.empty()) {
3513
0
            if (res2.size() == 2 && source == "ESRI") {
3514
0
                std::list<std::string> l;
3515
0
                l.emplace_back(res2.front()[0]);
3516
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3517
0
                const auto uniqueEsriAlias = getUniqueEsriAlias(l);
3518
0
                if (!uniqueEsriAlias.empty())
3519
0
                    return uniqueEsriAlias;
3520
0
            }
3521
0
            return res2.front()[0];
3522
0
        }
3523
0
    }
3524
0
    return std::string();
3525
0
}
3526
3527
// ---------------------------------------------------------------------------
3528
3529
/** \brief Gets the alias names for an object.
3530
 *
3531
 * Either authName + code or officialName must be non empty.
3532
 *
3533
 * @param authName Authority.
3534
 * @param code Code.
3535
 * @param officialName Official name.
3536
 * @param tableName Table name/category. Mandatory.
3537
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3538
 *                  accepted as special names to add a constraint on the "type"
3539
 *                  column of the "geodetic_crs" table.
3540
 * @param source Source of the alias. May be empty.
3541
 * @return Aliases
3542
 */
3543
std::list<std::string> DatabaseContext::getAliases(
3544
    const std::string &authName, const std::string &code,
3545
    const std::string &officialName, const std::string &tableName,
3546
0
    const std::string &source) const {
3547
3548
0
    std::list<std::string> res;
3549
0
    const auto key(authName + code + officialName + tableName + source);
3550
0
    if (d->cacheAliasNames_.tryGet(key, res)) {
3551
0
        return res;
3552
0
    }
3553
3554
0
    std::string resolvedAuthName(authName);
3555
0
    std::string resolvedCode(code);
3556
0
    const auto genuineTableName =
3557
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3558
0
            ? "geodetic_crs"
3559
0
            : tableName;
3560
0
    if (authName.empty() || code.empty()) {
3561
0
        std::string sql("SELECT auth_name, code FROM \"");
3562
0
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3563
0
        sql += "\" WHERE name = ?";
3564
0
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3565
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3566
0
        } else if (tableName == "geographic_3D_crs") {
3567
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3568
0
        }
3569
0
        sql += " ORDER BY deprecated";
3570
0
        auto resSql = d->run(sql, {officialName});
3571
0
        if (resSql.empty()) {
3572
0
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3573
0
                            "table_name = ? AND "
3574
0
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3575
0
                            {genuineTableName, officialName});
3576
0
            if (resSql.size() != 1) {
3577
0
                d->cacheAliasNames_.insert(key, res);
3578
0
                return res;
3579
0
            }
3580
0
        }
3581
0
        const auto &row = resSql.front();
3582
0
        resolvedAuthName = row[0];
3583
0
        resolvedCode = row[1];
3584
0
    }
3585
0
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3586
0
                    "auth_name = ? AND code = ?");
3587
0
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3588
0
    if (!source.empty()) {
3589
0
        sql += " AND source = ?";
3590
0
        params.emplace_back(source);
3591
0
    }
3592
0
    auto resSql = d->run(sql, params);
3593
0
    for (const auto &row : resSql) {
3594
0
        res.emplace_back(row[0]);
3595
0
    }
3596
3597
0
    if (res.size() == 2 && source == "ESRI") {
3598
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3599
0
        if (!uniqueEsriAlias.empty()) {
3600
0
            res.clear();
3601
0
            res.emplace_back(uniqueEsriAlias);
3602
0
        }
3603
0
    }
3604
3605
0
    d->cacheAliasNames_.insert(key, res);
3606
0
    return res;
3607
0
}
3608
3609
// ---------------------------------------------------------------------------
3610
3611
/** \brief Return the 'name' column of a table for an object
3612
 *
3613
 * @param tableName Table name/category.
3614
 * @param authName Authority name of the object.
3615
 * @param code Code of the object
3616
 * @return Name (or empty)
3617
 * @throw FactoryException
3618
 */
3619
std::string DatabaseContext::getName(const std::string &tableName,
3620
                                     const std::string &authName,
3621
0
                                     const std::string &code) const {
3622
0
    std::string sql("SELECT name FROM \"");
3623
0
    sql += replaceAll(tableName, "\"", "\"\"");
3624
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3625
0
    auto res = d->run(sql, {authName, code});
3626
0
    if (res.empty()) {
3627
0
        return std::string();
3628
0
    }
3629
0
    return res.front()[0];
3630
0
}
3631
3632
// ---------------------------------------------------------------------------
3633
3634
/** \brief Return the 'text_definition' column of a table for an object
3635
 *
3636
 * @param tableName Table name/category.
3637
 * @param authName Authority name of the object.
3638
 * @param code Code of the object
3639
 * @return Text definition (or empty)
3640
 * @throw FactoryException
3641
 */
3642
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3643
                                               const std::string &authName,
3644
0
                                               const std::string &code) const {
3645
0
    std::string sql("SELECT text_definition FROM \"");
3646
0
    sql += replaceAll(tableName, "\"", "\"\"");
3647
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3648
0
    auto res = d->run(sql, {authName, code});
3649
0
    if (res.empty()) {
3650
0
        return std::string();
3651
0
    }
3652
0
    return res.front()[0];
3653
0
}
3654
3655
// ---------------------------------------------------------------------------
3656
3657
/** \brief Return the allowed authorities when researching transformations
3658
 * between different authorities.
3659
 *
3660
 * @throw FactoryException
3661
 */
3662
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3663
    const std::string &sourceAuthName,
3664
0
    const std::string &targetAuthName) const {
3665
3666
0
    const auto key(sourceAuthName + targetAuthName);
3667
0
    auto hit = d->cacheAllowedAuthorities_.find(key);
3668
0
    if (hit != d->cacheAllowedAuthorities_.end()) {
3669
0
        return hit->second;
3670
0
    }
3671
3672
0
    auto sqlRes = d->run(
3673
0
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3674
0
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3675
0
        {sourceAuthName, targetAuthName});
3676
0
    if (sqlRes.empty()) {
3677
0
        sqlRes = d->run(
3678
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3679
0
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3680
0
            {sourceAuthName});
3681
0
    }
3682
0
    if (sqlRes.empty()) {
3683
0
        sqlRes = d->run(
3684
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3685
0
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3686
0
            {targetAuthName});
3687
0
    }
3688
0
    if (sqlRes.empty()) {
3689
0
        sqlRes = d->run(
3690
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3691
0
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3692
0
            {});
3693
0
    }
3694
0
    if (sqlRes.empty()) {
3695
0
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3696
0
        return std::vector<std::string>();
3697
0
    }
3698
0
    auto res = split(sqlRes.front()[0], ',');
3699
0
    d->cacheAllowedAuthorities_[key] = res;
3700
0
    return res;
3701
0
}
3702
3703
// ---------------------------------------------------------------------------
3704
3705
std::list<std::pair<std::string, std::string>>
3706
DatabaseContext::getNonDeprecated(const std::string &tableName,
3707
                                  const std::string &authName,
3708
0
                                  const std::string &code) const {
3709
0
    auto sqlRes =
3710
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3711
0
               "FROM deprecation "
3712
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3713
0
               "AND deprecated_code = ?",
3714
0
               {tableName, authName, code});
3715
0
    std::list<std::pair<std::string, std::string>> res;
3716
0
    for (const auto &row : sqlRes) {
3717
0
        const auto &source = row[2];
3718
0
        if (source == "PROJ") {
3719
0
            const auto &replacement_auth_name = row[0];
3720
0
            const auto &replacement_code = row[1];
3721
0
            res.emplace_back(replacement_auth_name, replacement_code);
3722
0
        }
3723
0
    }
3724
0
    if (!res.empty()) {
3725
0
        return res;
3726
0
    }
3727
0
    for (const auto &row : sqlRes) {
3728
0
        const auto &replacement_auth_name = row[0];
3729
0
        const auto &replacement_code = row[1];
3730
0
        res.emplace_back(replacement_auth_name, replacement_code);
3731
0
    }
3732
0
    return res;
3733
0
}
3734
3735
// ---------------------------------------------------------------------------
3736
3737
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3738
0
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3739
0
    if (cacheAuthNameWithVersion_.empty()) {
3740
0
        const auto sqlRes =
3741
0
            run("SELECT versioned_auth_name, auth_name, version, priority "
3742
0
                "FROM versioned_auth_name_mapping");
3743
0
        for (const auto &row : sqlRes) {
3744
0
            VersionedAuthName van;
3745
0
            van.versionedAuthName = row[0];
3746
0
            van.authName = row[1];
3747
0
            van.version = row[2];
3748
0
            van.priority = atoi(row[3].c_str());
3749
0
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3750
0
        }
3751
0
    }
3752
0
    return cacheAuthNameWithVersion_;
3753
0
}
3754
3755
// ---------------------------------------------------------------------------
3756
3757
// From IAU_2015 returns (IAU,2015)
3758
bool DatabaseContext::getAuthorityAndVersion(
3759
    const std::string &versionedAuthName, std::string &authNameOut,
3760
0
    std::string &versionOut) {
3761
3762
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3763
0
        if (van.versionedAuthName == versionedAuthName) {
3764
0
            authNameOut = van.authName;
3765
0
            versionOut = van.version;
3766
0
            return true;
3767
0
        }
3768
0
    }
3769
0
    return false;
3770
0
}
3771
3772
// ---------------------------------------------------------------------------
3773
3774
// From IAU and 2015, returns IAU_2015
3775
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3776
                                            const std::string &version,
3777
0
                                            std::string &versionedAuthNameOut) {
3778
3779
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3780
0
        if (van.authName == authName && van.version == version) {
3781
0
            versionedAuthNameOut = van.versionedAuthName;
3782
0
            return true;
3783
0
        }
3784
0
    }
3785
0
    return false;
3786
0
}
3787
3788
// ---------------------------------------------------------------------------
3789
3790
// From IAU returns IAU_latest, ... IAU_2015
3791
std::vector<std::string>
3792
0
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3793
3794
0
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3795
0
    std::vector<VersionedAuthNamePriority> tmp;
3796
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3797
0
        if (van.authName == authName) {
3798
0
            tmp.emplace_back(
3799
0
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3800
0
        }
3801
0
    }
3802
0
    std::vector<std::string> res;
3803
0
    if (!tmp.empty()) {
3804
        // Sort by decreasing priority
3805
0
        std::sort(tmp.begin(), tmp.end(),
3806
0
                  [](const VersionedAuthNamePriority &a,
3807
0
                     const VersionedAuthNamePriority &b) {
3808
0
                      return b.second > a.second;
3809
0
                  });
3810
0
        for (const auto &pair : tmp)
3811
0
            res.emplace_back(pair.first);
3812
0
    }
3813
0
    return res;
3814
0
}
3815
3816
// ---------------------------------------------------------------------------
3817
3818
std::vector<operation::CoordinateOperationNNPtr>
3819
DatabaseContext::getTransformationsForGridName(
3820
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3821
0
    auto sqlRes = databaseContext->d->run(
3822
0
        "SELECT auth_name, code FROM grid_transformation "
3823
0
        "WHERE grid_name = ? OR grid_name IN "
3824
0
        "(SELECT original_grid_name FROM grid_alternatives "
3825
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3826
0
        {gridName, gridName});
3827
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3828
0
    for (const auto &row : sqlRes) {
3829
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3830
0
                             ->createCoordinateOperation(row[1], true));
3831
0
    }
3832
0
    return res;
3833
0
}
3834
3835
//! @endcond
3836
3837
// ---------------------------------------------------------------------------
3838
3839
//! @cond Doxygen_Suppress
3840
struct AuthorityFactory::Private {
3841
    Private(const DatabaseContextNNPtr &contextIn,
3842
            const std::string &authorityName)
3843
0
        : context_(contextIn), authority_(authorityName) {}
3844
3845
0
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
3846
3847
0
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
3848
0
        return context_;
3849
0
    }
3850
3851
    // cppcheck-suppress functionStatic
3852
0
    void setThis(AuthorityFactoryNNPtr factory) {
3853
0
        thisFactory_ = factory.as_nullable();
3854
0
    }
3855
3856
    // cppcheck-suppress functionStatic
3857
0
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
3858
3859
0
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
3860
0
        if (auth_name == authority_) {
3861
0
            return NN_NO_CHECK(thisFactory_.lock());
3862
0
        }
3863
0
        return AuthorityFactory::create(context_, auth_name);
3864
0
    }
3865
3866
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
3867
                                  bool considerKnownGridsAsAvailable);
3868
3869
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
3870
                                      const std::string &code);
3871
3872
    util::PropertyMap
3873
    createProperties(const std::string &code, const std::string &name,
3874
                     bool deprecated,
3875
                     const std::vector<ObjectDomainNNPtr> &usages);
3876
3877
    util::PropertyMap
3878
    createPropertiesSearchUsages(const std::string &table_name,
3879
                                 const std::string &code,
3880
                                 const std::string &name, bool deprecated);
3881
3882
    util::PropertyMap createPropertiesSearchUsages(
3883
        const std::string &table_name, const std::string &code,
3884
        const std::string &name, bool deprecated, const std::string &remarks);
3885
3886
    SQLResultSet run(const std::string &sql,
3887
                     const ListOfParams &parameters = ListOfParams());
3888
3889
    SQLResultSet runWithCodeParam(const std::string &sql,
3890
                                  const std::string &code);
3891
3892
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
3893
3894
0
    bool hasAuthorityRestriction() const {
3895
0
        return !authority_.empty() && authority_ != "any";
3896
0
    }
3897
3898
    SQLResultSet createProjectedCRSBegin(const std::string &code);
3899
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
3900
                                                 const SQLResultSet &res);
3901
3902
  private:
3903
    DatabaseContextNNPtr context_;
3904
    std::string authority_;
3905
    std::weak_ptr<AuthorityFactory> thisFactory_{};
3906
};
3907
3908
// ---------------------------------------------------------------------------
3909
3910
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
3911
0
                                            const ListOfParams &parameters) {
3912
0
    return context()->getPrivate()->run(sql, parameters);
3913
0
}
3914
3915
// ---------------------------------------------------------------------------
3916
3917
SQLResultSet
3918
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
3919
0
                                            const std::string &code) {
3920
0
    return run(sql, {authority(), code});
3921
0
}
3922
3923
// ---------------------------------------------------------------------------
3924
3925
SQLResultSet
3926
AuthorityFactory::Private::runWithCodeParam(const char *sql,
3927
0
                                            const std::string &code) {
3928
0
    return runWithCodeParam(std::string(sql), code);
3929
0
}
3930
3931
// ---------------------------------------------------------------------------
3932
3933
UnitOfMeasure
3934
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
3935
0
                                               const std::string &code) {
3936
0
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
3937
0
}
3938
3939
// ---------------------------------------------------------------------------
3940
3941
util::PropertyMap AuthorityFactory::Private::createProperties(
3942
    const std::string &code, const std::string &name, bool deprecated,
3943
0
    const std::vector<ObjectDomainNNPtr> &usages) {
3944
0
    auto props = util::PropertyMap()
3945
0
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
3946
0
                     .set(metadata::Identifier::CODE_KEY, code)
3947
0
                     .set(common::IdentifiedObject::NAME_KEY, name);
3948
0
    if (deprecated) {
3949
0
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
3950
0
    }
3951
0
    if (!usages.empty()) {
3952
3953
0
        auto array(util::ArrayOfBaseObject::create());
3954
0
        for (const auto &usage : usages) {
3955
0
            array->add(usage);
3956
0
        }
3957
0
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
3958
0
                  util::nn_static_pointer_cast<util::BaseObject>(array));
3959
0
    }
3960
0
    return props;
3961
0
}
3962
3963
// ---------------------------------------------------------------------------
3964
3965
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
3966
    const std::string &table_name, const std::string &code,
3967
0
    const std::string &name, bool deprecated) {
3968
3969
0
    SQLResultSet res;
3970
0
    if (table_name == "geodetic_crs" && code == "4326" &&
3971
0
        authority() == "EPSG") {
3972
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
3973
        // description is super verbose.
3974
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
3975
        // To avoid churn in our WKT2 output, hot patch to the usage of
3976
        // 10.076 and earlier
3977
0
        res = run("SELECT extent.description, extent.south_lat, "
3978
0
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
3979
0
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
3980
0
                  "extent.code = 1262 and scope.code = 1183");
3981
0
    } else {
3982
0
        const std::string sql(
3983
0
            "SELECT extent.description, extent.south_lat, "
3984
0
            "extent.north_lat, extent.west_lon, extent.east_lon, "
3985
0
            "scope.scope, "
3986
0
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
3987
0
            "AS score "
3988
0
            "FROM usage "
3989
0
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
3990
0
            "usage.extent_code = extent.code "
3991
0
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
3992
0
            "usage.scope_code = scope.code "
3993
0
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
3994
0
            "object_code = ? AND "
3995
            // We voluntary exclude extent and scope with a specific code
3996
0
            "NOT (usage.extent_auth_name = 'PROJ' AND "
3997
0
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
3998
0
            "NOT (usage.scope_auth_name = 'PROJ' AND "
3999
0
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4000
0
            "ORDER BY score, usage.auth_name, usage.code");
4001
0
        res = run(sql, {table_name, authority(), code});
4002
0
    }
4003
0
    std::vector<ObjectDomainNNPtr> usages;
4004
0
    for (const auto &row : res) {
4005
0
        try {
4006
0
            size_t idx = 0;
4007
0
            const auto &extent_description = row[idx++];
4008
0
            const auto &south_lat_str = row[idx++];
4009
0
            const auto &north_lat_str = row[idx++];
4010
0
            const auto &west_lon_str = row[idx++];
4011
0
            const auto &east_lon_str = row[idx++];
4012
0
            const auto &scope = row[idx];
4013
4014
0
            util::optional<std::string> scopeOpt;
4015
0
            if (!scope.empty()) {
4016
0
                scopeOpt = scope;
4017
0
            }
4018
4019
0
            metadata::ExtentPtr extent;
4020
0
            if (south_lat_str.empty()) {
4021
0
                extent = metadata::Extent::create(
4022
0
                             util::optional<std::string>(extent_description),
4023
0
                             {}, {}, {})
4024
0
                             .as_nullable();
4025
0
            } else {
4026
0
                double south_lat = c_locale_stod(south_lat_str);
4027
0
                double north_lat = c_locale_stod(north_lat_str);
4028
0
                double west_lon = c_locale_stod(west_lon_str);
4029
0
                double east_lon = c_locale_stod(east_lon_str);
4030
0
                auto bbox = metadata::GeographicBoundingBox::create(
4031
0
                    west_lon, south_lat, east_lon, north_lat);
4032
0
                extent = metadata::Extent::create(
4033
0
                             util::optional<std::string>(extent_description),
4034
0
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4035
0
                             std::vector<metadata::VerticalExtentNNPtr>(),
4036
0
                             std::vector<metadata::TemporalExtentNNPtr>())
4037
0
                             .as_nullable();
4038
0
            }
4039
4040
0
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4041
0
        } catch (const std::exception &) {
4042
0
        }
4043
0
    }
4044
0
    return createProperties(code, name, deprecated, std::move(usages));
4045
0
}
4046
4047
// ---------------------------------------------------------------------------
4048
4049
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4050
    const std::string &table_name, const std::string &code,
4051
0
    const std::string &name, bool deprecated, const std::string &remarks) {
4052
0
    auto props =
4053
0
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4054
0
    if (!remarks.empty())
4055
0
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4056
0
    return props;
4057
0
}
4058
4059
// ---------------------------------------------------------------------------
4060
4061
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4062
    const operation::CoordinateOperationNNPtr &op,
4063
0
    bool considerKnownGridsAsAvailable) {
4064
4065
0
    struct DisableNetwork {
4066
0
        const DatabaseContextNNPtr &m_dbContext;
4067
0
        bool m_old_network_enabled = false;
4068
4069
0
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4070
0
            : m_dbContext(l_context) {
4071
0
            auto ctxt = m_dbContext->d->pjCtxt();
4072
0
            if (ctxt == nullptr) {
4073
0
                ctxt = pj_get_default_ctx();
4074
0
                m_dbContext->d->setPjCtxt(ctxt);
4075
0
            }
4076
0
            m_old_network_enabled =
4077
0
                proj_context_is_network_enabled(ctxt) != FALSE;
4078
0
            if (m_old_network_enabled)
4079
0
                proj_context_set_enable_network(ctxt, false);
4080
0
        }
4081
4082
0
        ~DisableNetwork() {
4083
0
            if (m_old_network_enabled) {
4084
0
                auto ctxt = m_dbContext->d->pjCtxt();
4085
0
                proj_context_set_enable_network(ctxt, true);
4086
0
            }
4087
0
        }
4088
0
    };
4089
4090
0
    auto &l_context = context();
4091
    // Temporarily disable networking as we are only interested in known grids
4092
0
    DisableNetwork disabler(l_context);
4093
4094
0
    for (const auto &gridDesc :
4095
0
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4096
0
        if (!gridDesc.available) {
4097
0
            return true;
4098
0
        }
4099
0
    }
4100
0
    return false;
4101
0
}
4102
4103
//! @endcond
4104
4105
// ---------------------------------------------------------------------------
4106
4107
//! @cond Doxygen_Suppress
4108
0
AuthorityFactory::~AuthorityFactory() = default;
4109
//! @endcond
4110
4111
// ---------------------------------------------------------------------------
4112
4113
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4114
                                   const std::string &authorityName)
4115
0
    : d(internal::make_unique<Private>(context, authorityName)) {}
4116
4117
// ---------------------------------------------------------------------------
4118
4119
// clang-format off
4120
/** \brief Instantiate a AuthorityFactory.
4121
 *
4122
 * The authority name might be set to the empty string in the particular case
4123
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4124
 * is called.
4125
 *
4126
 * @param context Context.
4127
 * @param authorityName Authority name.
4128
 * @return new AuthorityFactory.
4129
 */
4130
// clang-format on
4131
4132
AuthorityFactoryNNPtr
4133
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4134
0
                         const std::string &authorityName) {
4135
0
    const auto getFactory = [&context, &authorityName]() {
4136
0
        for (const auto &knownName :
4137
0
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4138
0
            if (ci_equal(authorityName, knownName)) {
4139
0
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4140
0
                    context, knownName);
4141
0
            }
4142
0
        }
4143
0
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4144
0
            context, authorityName);
4145
0
    };
4146
0
    auto factory = getFactory();
4147
0
    factory->d->setThis(factory);
4148
0
    return factory;
4149
0
}
4150
4151
// ---------------------------------------------------------------------------
4152
4153
/** \brief Returns the database context. */
4154
0
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4155
0
    return d->context();
4156
0
}
4157
4158
// ---------------------------------------------------------------------------
4159
4160
//! @cond Doxygen_Suppress
4161
AuthorityFactory::CRSInfo::CRSInfo()
4162
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4163
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4164
      north_lat_degree{}, areaName{}, projectionMethodName{},
4165
0
      celestialBodyName{} {}
4166
//! @endcond
4167
4168
// ---------------------------------------------------------------------------
4169
4170
/** \brief Returns an arbitrary object from a code.
4171
 *
4172
 * The returned object will typically be an instance of Datum,
4173
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4174
 * the object is know at compile time, it is recommended to invoke the most
4175
 * precise method instead of this one (for example
4176
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4177
 * if the caller know he is asking for a coordinate reference system).
4178
 *
4179
 * If there are several objects with the same code, a FactoryException is
4180
 * thrown.
4181
 *
4182
 * @param code Object code allocated by authority. (e.g. "4326")
4183
 * @return object.
4184
 * @throw NoSuchAuthorityCodeException
4185
 * @throw FactoryException
4186
 */
4187
4188
util::BaseObjectNNPtr
4189
0
AuthorityFactory::createObject(const std::string &code) const {
4190
4191
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4192
0
                                   "WHERE auth_name = ? AND code = ?",
4193
0
                                   code);
4194
0
    if (res.empty()) {
4195
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4196
0
    }
4197
0
    if (res.size() != 1) {
4198
0
        std::string msg(
4199
0
            "More than one object matching specified code. Objects found in ");
4200
0
        bool first = true;
4201
0
        for (const auto &row : res) {
4202
0
            if (!first)
4203
0
                msg += ", ";
4204
0
            msg += row[0];
4205
0
            first = false;
4206
0
        }
4207
0
        throw FactoryException(msg);
4208
0
    }
4209
0
    const auto &first_row = res.front();
4210
0
    const auto &table_name = first_row[0];
4211
0
    const auto &type = first_row[1];
4212
0
    if (table_name == "extent") {
4213
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4214
0
            createExtent(code));
4215
0
    }
4216
0
    if (table_name == "unit_of_measure") {
4217
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4218
0
            createUnitOfMeasure(code));
4219
0
    }
4220
0
    if (table_name == "prime_meridian") {
4221
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4222
0
            createPrimeMeridian(code));
4223
0
    }
4224
0
    if (table_name == "ellipsoid") {
4225
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4226
0
            createEllipsoid(code));
4227
0
    }
4228
0
    if (table_name == "geodetic_datum") {
4229
0
        if (type == "ensemble") {
4230
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4231
0
                createDatumEnsemble(code, table_name));
4232
0
        }
4233
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4234
0
            createGeodeticDatum(code));
4235
0
    }
4236
0
    if (table_name == "vertical_datum") {
4237
0
        if (type == "ensemble") {
4238
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4239
0
                createDatumEnsemble(code, table_name));
4240
0
        }
4241
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4242
0
            createVerticalDatum(code));
4243
0
    }
4244
0
    if (table_name == "geodetic_crs") {
4245
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4246
0
            createGeodeticCRS(code));
4247
0
    }
4248
0
    if (table_name == "vertical_crs") {
4249
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4250
0
            createVerticalCRS(code));
4251
0
    }
4252
0
    if (table_name == "projected_crs") {
4253
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4254
0
            createProjectedCRS(code));
4255
0
    }
4256
0
    if (table_name == "compound_crs") {
4257
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4258
0
            createCompoundCRS(code));
4259
0
    }
4260
0
    if (table_name == "conversion") {
4261
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4262
0
            createConversion(code));
4263
0
    }
4264
0
    if (table_name == "helmert_transformation" ||
4265
0
        table_name == "grid_transformation" ||
4266
0
        table_name == "other_transformation" ||
4267
0
        table_name == "concatenated_operation") {
4268
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4269
0
            createCoordinateOperation(code, false));
4270
0
    }
4271
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4272
0
}
4273
4274
// ---------------------------------------------------------------------------
4275
4276
//! @cond Doxygen_Suppress
4277
static FactoryException buildFactoryException(const char *type,
4278
                                              const std::string &authName,
4279
                                              const std::string &code,
4280
0
                                              const std::exception &ex) {
4281
0
    return FactoryException(std::string("cannot build ") + type + " " +
4282
0
                            authName + ":" + code + ": " + ex.what());
4283
0
}
4284
//! @endcond
4285
4286
// ---------------------------------------------------------------------------
4287
4288
/** \brief Returns a metadata::Extent from the specified code.
4289
 *
4290
 * @param code Object code allocated by authority.
4291
 * @return object.
4292
 * @throw NoSuchAuthorityCodeException
4293
 * @throw FactoryException
4294
 */
4295
4296
metadata::ExtentNNPtr
4297
0
AuthorityFactory::createExtent(const std::string &code) const {
4298
0
    const auto cacheKey(d->authority() + code);
4299
0
    {
4300
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4301
0
        if (extent) {
4302
0
            return NN_NO_CHECK(extent);
4303
0
        }
4304
0
    }
4305
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4306
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4307
0
    auto res = d->runWithCodeParam(sql, code);
4308
0
    if (res.empty()) {
4309
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4310
0
                                           code);
4311
0
    }
4312
0
    try {
4313
0
        const auto &row = res.front();
4314
0
        const auto &description = row[0];
4315
0
        if (row[1].empty()) {
4316
0
            auto extent = metadata::Extent::create(
4317
0
                util::optional<std::string>(description), {}, {}, {});
4318
0
            d->context()->d->cache(cacheKey, extent);
4319
0
            return extent;
4320
0
        }
4321
0
        double south_lat = c_locale_stod(row[1]);
4322
0
        double north_lat = c_locale_stod(row[2]);
4323
0
        double west_lon = c_locale_stod(row[3]);
4324
0
        double east_lon = c_locale_stod(row[4]);
4325
0
        auto bbox = metadata::GeographicBoundingBox::create(
4326
0
            west_lon, south_lat, east_lon, north_lat);
4327
4328
0
        auto extent = metadata::Extent::create(
4329
0
            util::optional<std::string>(description),
4330
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4331
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4332
0
            std::vector<metadata::TemporalExtentNNPtr>());
4333
0
        d->context()->d->cache(cacheKey, extent);
4334
0
        return extent;
4335
4336
0
    } catch (const std::exception &ex) {
4337
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4338
0
    }
4339
0
}
4340
4341
// ---------------------------------------------------------------------------
4342
4343
/** \brief Returns a common::UnitOfMeasure from the specified code.
4344
 *
4345
 * @param code Object code allocated by authority.
4346
 * @return object.
4347
 * @throw NoSuchAuthorityCodeException
4348
 * @throw FactoryException
4349
 */
4350
4351
UnitOfMeasureNNPtr
4352
0
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4353
0
    const auto cacheKey(d->authority() + code);
4354
0
    {
4355
0
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4356
0
        if (uom) {
4357
0
            return NN_NO_CHECK(uom);
4358
0
        }
4359
0
    }
4360
0
    auto res = d->context()->d->run(
4361
0
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4362
0
        "auth_name = ? AND code = ?",
4363
0
        {d->authority(), code}, true);
4364
0
    if (res.empty()) {
4365
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4366
0
                                           d->authority(), code);
4367
0
    }
4368
0
    try {
4369
0
        const auto &row = res.front();
4370
0
        const auto &name =
4371
0
            (row[0] == "degree (supplier to define representation)")
4372
0
                ? UnitOfMeasure::DEGREE.name()
4373
0
                : row[0];
4374
0
        double conv_factor = (code == "9107" || code == "9108")
4375
0
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4376
0
                                 : c_locale_stod(row[1]);
4377
0
        constexpr double EPS = 1e-10;
4378
0
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4379
0
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4380
0
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4381
0
        }
4382
0
        if (std::fabs(conv_factor -
4383
0
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4384
0
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4385
0
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4386
0
        }
4387
0
        const auto &type_str = row[2];
4388
0
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4389
0
        if (type_str == "length")
4390
0
            unitType = UnitOfMeasure::Type::LINEAR;
4391
0
        else if (type_str == "angle")
4392
0
            unitType = UnitOfMeasure::Type::ANGULAR;
4393
0
        else if (type_str == "scale")
4394
0
            unitType = UnitOfMeasure::Type::SCALE;
4395
0
        else if (type_str == "time")
4396
0
            unitType = UnitOfMeasure::Type::TIME;
4397
0
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4398
0
            name, conv_factor, unitType, d->authority(), code);
4399
0
        d->context()->d->cache(cacheKey, uom);
4400
0
        return uom;
4401
0
    } catch (const std::exception &ex) {
4402
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4403
0
                                    ex);
4404
0
    }
4405
0
}
4406
4407
// ---------------------------------------------------------------------------
4408
4409
//! @cond Doxygen_Suppress
4410
static double normalizeMeasure(const std::string &uom_code,
4411
                               const std::string &value,
4412
0
                               std::string &normalized_uom_code) {
4413
0
    if (uom_code == "9110") // DDD.MMSSsss.....
4414
0
    {
4415
0
        double normalized_value = c_locale_stod(value);
4416
0
        std::ostringstream buffer;
4417
0
        buffer.imbue(std::locale::classic());
4418
0
        constexpr size_t precision = 12;
4419
0
        buffer << std::fixed << std::setprecision(precision)
4420
0
               << normalized_value;
4421
0
        auto formatted = buffer.str();
4422
0
        size_t dotPos = formatted.find('.');
4423
0
        assert(dotPos + 1 + precision == formatted.size());
4424
0
        auto minutes = formatted.substr(dotPos + 1, 2);
4425
0
        auto seconds = formatted.substr(dotPos + 3);
4426
0
        assert(seconds.size() == precision - 2);
4427
0
        normalized_value =
4428
0
            (normalized_value < 0 ? -1.0 : 1.0) *
4429
0
            (std::floor(std::fabs(normalized_value)) +
4430
0
             c_locale_stod(minutes) / 60. +
4431
0
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4432
0
                 3600.);
4433
0
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4434
        /* coverity[overflow_sink] */
4435
0
        return normalized_value;
4436
0
    } else {
4437
0
        normalized_uom_code = uom_code;
4438
0
        return c_locale_stod(value);
4439
0
    }
4440
0
}
4441
//! @endcond
4442
4443
// ---------------------------------------------------------------------------
4444
4445
/** \brief Returns a datum::PrimeMeridian from the specified code.
4446
 *
4447
 * @param code Object code allocated by authority.
4448
 * @return object.
4449
 * @throw NoSuchAuthorityCodeException
4450
 * @throw FactoryException
4451
 */
4452
4453
datum::PrimeMeridianNNPtr
4454
0
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4455
0
    const auto cacheKey(d->authority() + code);
4456
0
    {
4457
0
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4458
0
        if (pm) {
4459
0
            return NN_NO_CHECK(pm);
4460
0
        }
4461
0
    }
4462
0
    auto res = d->runWithCodeParam(
4463
0
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4464
0
        "prime_meridian WHERE "
4465
0
        "auth_name = ? AND code = ?",
4466
0
        code);
4467
0
    if (res.empty()) {
4468
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4469
0
                                           d->authority(), code);
4470
0
    }
4471
0
    try {
4472
0
        const auto &row = res.front();
4473
0
        const auto &name = row[0];
4474
0
        const auto &longitude = row[1];
4475
0
        const auto &uom_auth_name = row[2];
4476
0
        const auto &uom_code = row[3];
4477
0
        const bool deprecated = row[4] == "1";
4478
4479
0
        std::string normalized_uom_code(uom_code);
4480
0
        const double normalized_value =
4481
0
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4482
4483
0
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4484
0
        auto props = d->createProperties(code, name, deprecated, {});
4485
0
        auto pm = datum::PrimeMeridian::create(
4486
0
            props, common::Angle(normalized_value, uom));
4487
0
        d->context()->d->cache(cacheKey, pm);
4488
0
        return pm;
4489
0
    } catch (const std::exception &ex) {
4490
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4491
0
    }
4492
0
}
4493
4494
// ---------------------------------------------------------------------------
4495
4496
/** \brief Identify a celestial body from an approximate radius.
4497
 *
4498
 * @param semi_major_axis Approximate semi-major axis.
4499
 * @param tolerance Relative error allowed.
4500
 * @return celestial body name if one single match found.
4501
 * @throw FactoryException
4502
 */
4503
4504
std::string
4505
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4506
0
                                                double tolerance) const {
4507
0
    auto res =
4508
0
        d->run("SELECT name, (ABS(semi_major_axis - ?) / semi_major_axis ) "
4509
0
               "AS rel_error FROM celestial_body WHERE rel_error <= ?",
4510
0
               {semi_major_axis, tolerance});
4511
0
    if (res.empty()) {
4512
0
        throw FactoryException("no match found");
4513
0
    }
4514
0
    if (res.size() > 1) {
4515
0
        for (const auto &row : res) {
4516
0
            if (row[0] != res.front()[0]) {
4517
0
                throw FactoryException("more than one match found");
4518
0
            }
4519
0
        }
4520
0
    }
4521
0
    return res.front()[0];
4522
0
}
4523
4524
// ---------------------------------------------------------------------------
4525
4526
/** \brief Returns a datum::Ellipsoid from the specified code.
4527
 *
4528
 * @param code Object code allocated by authority.
4529
 * @return object.
4530
 * @throw NoSuchAuthorityCodeException
4531
 * @throw FactoryException
4532
 */
4533
4534
datum::EllipsoidNNPtr
4535
0
AuthorityFactory::createEllipsoid(const std::string &code) const {
4536
0
    const auto cacheKey(d->authority() + code);
4537
0
    {
4538
0
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4539
0
        if (ellps) {
4540
0
            return NN_NO_CHECK(ellps);
4541
0
        }
4542
0
    }
4543
0
    auto res = d->runWithCodeParam(
4544
0
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4545
0
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4546
0
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4547
0
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4548
0
        "ellipsoid JOIN celestial_body "
4549
0
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4550
0
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4551
0
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4552
0
        code);
4553
0
    if (res.empty()) {
4554
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4555
0
                                           d->authority(), code);
4556
0
    }
4557
0
    try {
4558
0
        const auto &row = res.front();
4559
0
        const auto &name = row[0];
4560
0
        const auto &semi_major_axis_str = row[1];
4561
0
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4562
0
        const auto &uom_auth_name = row[2];
4563
0
        const auto &uom_code = row[3];
4564
0
        const auto &inv_flattening_str = row[4];
4565
0
        const auto &semi_minor_axis_str = row[5];
4566
0
        const auto &body = row[6];
4567
0
        const bool deprecated = row[7] == "1";
4568
0
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4569
0
        auto props = d->createProperties(code, name, deprecated, {});
4570
0
        if (!inv_flattening_str.empty()) {
4571
0
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4572
0
                props, common::Length(semi_major_axis, uom),
4573
0
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4574
0
            d->context()->d->cache(cacheKey, ellps);
4575
0
            return ellps;
4576
0
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4577
0
            auto ellps = datum::Ellipsoid::createSphere(
4578
0
                props, common::Length(semi_major_axis, uom), body);
4579
0
            d->context()->d->cache(cacheKey, ellps);
4580
0
            return ellps;
4581
0
        } else {
4582
0
            auto ellps = datum::Ellipsoid::createTwoAxis(
4583
0
                props, common::Length(semi_major_axis, uom),
4584
0
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4585
0
            d->context()->d->cache(cacheKey, ellps);
4586
0
            return ellps;
4587
0
        }
4588
0
    } catch (const std::exception &ex) {
4589
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4590
0
    }
4591
0
}
4592
4593
// ---------------------------------------------------------------------------
4594
4595
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4596
 *
4597
 * @param code Object code allocated by authority.
4598
 * @return object.
4599
 * @throw NoSuchAuthorityCodeException
4600
 * @throw FactoryException
4601
 */
4602
4603
datum::GeodeticReferenceFrameNNPtr
4604
0
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4605
4606
0
    datum::GeodeticReferenceFramePtr datum;
4607
0
    datum::DatumEnsemblePtr datumEnsemble;
4608
0
    constexpr bool turnEnsembleAsDatum = true;
4609
0
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4610
0
                                  turnEnsembleAsDatum);
4611
0
    return NN_NO_CHECK(datum);
4612
0
}
4613
4614
// ---------------------------------------------------------------------------
4615
4616
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4617
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4618
0
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4619
0
    const auto cacheKey(d->authority() + code);
4620
0
    {
4621
0
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4622
0
        if (outDatumEnsemble) {
4623
0
            if (!turnEnsembleAsDatum)
4624
0
                return;
4625
0
            outDatumEnsemble = nullptr;
4626
0
        }
4627
0
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4628
0
        if (outDatum) {
4629
0
            return;
4630
0
        }
4631
0
    }
4632
0
    auto res = d->runWithCodeParam(
4633
0
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4634
0
        "prime_meridian_auth_name, prime_meridian_code, "
4635
0
        "publication_date, frame_reference_epoch, "
4636
0
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4637
0
        "FROM geodetic_datum "
4638
0
        "WHERE "
4639
0
        "auth_name = ? AND code = ?",
4640
0
        code);
4641
0
    if (res.empty()) {
4642
0
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4643
0
                                           d->authority(), code);
4644
0
    }
4645
0
    try {
4646
0
        const auto &row = res.front();
4647
0
        const auto &name = row[0];
4648
0
        const auto &ellipsoid_auth_name = row[1];
4649
0
        const auto &ellipsoid_code = row[2];
4650
0
        const auto &prime_meridian_auth_name = row[3];
4651
0
        const auto &prime_meridian_code = row[4];
4652
0
        const auto &publication_date = row[5];
4653
0
        const auto &frame_reference_epoch = row[6];
4654
0
        const auto &ensemble_accuracy = row[7];
4655
0
        const auto &anchor = row[8];
4656
0
        const auto &anchor_epoch = row[9];
4657
0
        const bool deprecated = row[10] == "1";
4658
4659
0
        std::string massagedName = name;
4660
0
        if (turnEnsembleAsDatum) {
4661
0
            if (name == "World Geodetic System 1984 ensemble") {
4662
0
                massagedName = "World Geodetic System 1984";
4663
0
            } else if (name ==
4664
0
                       "European Terrestrial Reference System 1989 ensemble") {
4665
0
                massagedName = "European Terrestrial Reference System 1989";
4666
0
            }
4667
0
        }
4668
0
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4669
0
                                                     massagedName, deprecated);
4670
4671
0
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4672
0
            auto resMembers =
4673
0
                d->run("SELECT member_auth_name, member_code FROM "
4674
0
                       "geodetic_datum_ensemble_member WHERE "
4675
0
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4676
0
                       "ORDER BY sequence",
4677
0
                       {d->authority(), code});
4678
4679
0
            std::vector<datum::DatumNNPtr> members;
4680
0
            for (const auto &memberRow : resMembers) {
4681
0
                members.push_back(
4682
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4683
0
            }
4684
0
            auto datumEnsemble = datum::DatumEnsemble::create(
4685
0
                props, std::move(members),
4686
0
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4687
0
            d->context()->d->cache(cacheKey, datumEnsemble);
4688
0
            outDatumEnsemble = datumEnsemble.as_nullable();
4689
0
        } else {
4690
0
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4691
0
                                 ->createEllipsoid(ellipsoid_code);
4692
0
            auto pm = d->createFactory(prime_meridian_auth_name)
4693
0
                          ->createPrimeMeridian(prime_meridian_code);
4694
4695
0
            auto anchorOpt = util::optional<std::string>();
4696
0
            if (!anchor.empty())
4697
0
                anchorOpt = anchor;
4698
0
            if (!publication_date.empty()) {
4699
0
                props.set("PUBLICATION_DATE", publication_date);
4700
0
            }
4701
0
            if (!anchor_epoch.empty()) {
4702
0
                props.set("ANCHOR_EPOCH", anchor_epoch);
4703
0
            }
4704
0
            auto datum = frame_reference_epoch.empty()
4705
0
                             ? datum::GeodeticReferenceFrame::create(
4706
0
                                   props, ellipsoid, anchorOpt, pm)
4707
0
                             : util::nn_static_pointer_cast<
4708
0
                                   datum::GeodeticReferenceFrame>(
4709
0
                                   datum::DynamicGeodeticReferenceFrame::create(
4710
0
                                       props, ellipsoid, anchorOpt, pm,
4711
0
                                       common::Measure(
4712
0
                                           c_locale_stod(frame_reference_epoch),
4713
0
                                           common::UnitOfMeasure::YEAR),
4714
0
                                       util::optional<std::string>()));
4715
0
            d->context()->d->cache(cacheKey, datum);
4716
0
            outDatum = datum.as_nullable();
4717
0
        }
4718
0
    } catch (const std::exception &ex) {
4719
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
4720
0
                                    code, ex);
4721
0
    }
4722
0
}
4723
4724
// ---------------------------------------------------------------------------
4725
4726
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
4727
 *
4728
 * @param code Object code allocated by authority.
4729
 * @return object.
4730
 * @throw NoSuchAuthorityCodeException
4731
 * @throw FactoryException
4732
 */
4733
4734
datum::VerticalReferenceFrameNNPtr
4735
0
AuthorityFactory::createVerticalDatum(const std::string &code) const {
4736
0
    datum::VerticalReferenceFramePtr datum;
4737
0
    datum::DatumEnsemblePtr datumEnsemble;
4738
0
    constexpr bool turnEnsembleAsDatum = true;
4739
0
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
4740
0
                                  turnEnsembleAsDatum);
4741
0
    return NN_NO_CHECK(datum);
4742
0
}
4743
4744
// ---------------------------------------------------------------------------
4745
4746
void AuthorityFactory::createVerticalDatumOrEnsemble(
4747
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
4748
0
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4749
0
    auto res =
4750
0
        d->runWithCodeParam("SELECT name, publication_date, "
4751
0
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
4752
0
                            "anchor_epoch, deprecated FROM "
4753
0
                            "vertical_datum WHERE auth_name = ? AND code = ?",
4754
0
                            code);
4755
0
    if (res.empty()) {
4756
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
4757
0
                                           d->authority(), code);
4758
0
    }
4759
0
    try {
4760
0
        const auto &row = res.front();
4761
0
        const auto &name = row[0];
4762
0
        const auto &publication_date = row[1];
4763
0
        const auto &frame_reference_epoch = row[2];
4764
0
        const auto &ensemble_accuracy = row[3];
4765
0
        const auto &anchor = row[4];
4766
0
        const auto &anchor_epoch = row[5];
4767
0
        const bool deprecated = row[6] == "1";
4768
0
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
4769
0
                                                     name, deprecated);
4770
0
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4771
0
            auto resMembers =
4772
0
                d->run("SELECT member_auth_name, member_code FROM "
4773
0
                       "vertical_datum_ensemble_member WHERE "
4774
0
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4775
0
                       "ORDER BY sequence",
4776
0
                       {d->authority(), code});
4777
4778
0
            std::vector<datum::DatumNNPtr> members;
4779
0
            for (const auto &memberRow : resMembers) {
4780
0
                members.push_back(
4781
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4782
0
            }
4783
0
            auto datumEnsemble = datum::DatumEnsemble::create(
4784
0
                props, std::move(members),
4785
0
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4786
0
            outDatumEnsemble = datumEnsemble.as_nullable();
4787
0
        } else {
4788
0
            if (!publication_date.empty()) {
4789
0
                props.set("PUBLICATION_DATE", publication_date);
4790
0
            }
4791
0
            if (!anchor_epoch.empty()) {
4792
0
                props.set("ANCHOR_EPOCH", anchor_epoch);
4793
0
            }
4794
0
            if (d->authority() == "ESRI" &&
4795
0
                starts_with(code, "from_geogdatum_")) {
4796
0
                props.set("VERT_DATUM_TYPE", "2002");
4797
0
            }
4798
0
            auto anchorOpt = util::optional<std::string>();
4799
0
            if (!anchor.empty())
4800
0
                anchorOpt = anchor;
4801
0
            if (frame_reference_epoch.empty()) {
4802
0
                outDatum =
4803
0
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
4804
0
                        .as_nullable();
4805
0
            } else {
4806
0
                outDatum =
4807
0
                    datum::DynamicVerticalReferenceFrame::create(
4808
0
                        props, anchorOpt,
4809
0
                        util::optional<datum::RealizationMethod>(),
4810
0
                        common::Measure(c_locale_stod(frame_reference_epoch),
4811
0
                                        common::UnitOfMeasure::YEAR),
4812
0
                        util::optional<std::string>())
4813
0
                        .as_nullable();
4814
0
            }
4815
0
        }
4816
0
    } catch (const std::exception &ex) {
4817
0
        throw buildFactoryException("vertical reference frame", d->authority(),
4818
0
                                    code, ex);
4819
0
    }
4820
0
}
4821
4822
// ---------------------------------------------------------------------------
4823
4824
/** \brief Returns a datum::DatumEnsemble from the specified code.
4825
 *
4826
 * @param code Object code allocated by authority.
4827
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
4828
 * @return object.
4829
 * @throw NoSuchAuthorityCodeException
4830
 * @throw FactoryException
4831
 */
4832
4833
datum::DatumEnsembleNNPtr
4834
AuthorityFactory::createDatumEnsemble(const std::string &code,
4835
0
                                      const std::string &type) const {
4836
0
    auto res = d->run(
4837
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
4838
0
        "geodetic_datum WHERE "
4839
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
4840
0
        "UNION ALL "
4841
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
4842
0
        "vertical_datum WHERE "
4843
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
4844
0
        {d->authority(), code, d->authority(), code});
4845
0
    if (res.empty()) {
4846
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
4847
0
                                           d->authority(), code);
4848
0
    }
4849
0
    for (const auto &row : res) {
4850
0
        const std::string &gotType = row[0];
4851
0
        const std::string &name = row[1];
4852
0
        const std::string &ensembleAccuracy = row[2];
4853
0
        const bool deprecated = row[3] == "1";
4854
0
        if (type.empty() || type == gotType) {
4855
0
            auto resMembers =
4856
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
4857
0
                           "_ensemble_member WHERE "
4858
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
4859
0
                           "ORDER BY sequence",
4860
0
                       {d->authority(), code});
4861
4862
0
            std::vector<datum::DatumNNPtr> members;
4863
0
            for (const auto &memberRow : resMembers) {
4864
0
                members.push_back(
4865
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4866
0
            }
4867
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
4868
0
                                                         deprecated);
4869
0
            return datum::DatumEnsemble::create(
4870
0
                props, std::move(members),
4871
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
4872
0
        }
4873
0
    }
4874
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
4875
0
                                       d->authority(), code);
4876
0
}
4877
4878
// ---------------------------------------------------------------------------
4879
4880
/** \brief Returns a datum::Datum from the specified code.
4881
 *
4882
 * @param code Object code allocated by authority.
4883
 * @return object.
4884
 * @throw NoSuchAuthorityCodeException
4885
 * @throw FactoryException
4886
 */
4887
4888
0
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
4889
0
    auto res =
4890
0
        d->run("SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
4891
0
               "auth_name = ? AND code = ? "
4892
0
               "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
4893
0
               "auth_name = ? AND code = ?",
4894
0
               {d->authority(), code, d->authority(), code});
4895
0
    if (res.empty()) {
4896
0
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
4897
0
                                           code);
4898
0
    }
4899
0
    if (res.front()[0] == "geodetic_datum") {
4900
0
        return createGeodeticDatum(code);
4901
0
    }
4902
0
    return createVerticalDatum(code);
4903
0
}
4904
4905
// ---------------------------------------------------------------------------
4906
4907
//! @cond Doxygen_Suppress
4908
0
static cs::MeridianPtr createMeridian(const std::string &val) {
4909
0
    try {
4910
0
        const std::string degW(std::string("\xC2\xB0") + "W");
4911
0
        if (ends_with(val, degW)) {
4912
0
            return cs::Meridian::create(common::Angle(
4913
0
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
4914
0
        }
4915
0
        const std::string degE(std::string("\xC2\xB0") + "E");
4916
0
        if (ends_with(val, degE)) {
4917
0
            return cs::Meridian::create(common::Angle(
4918
0
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
4919
0
        }
4920
0
    } catch (const std::exception &) {
4921
0
    }
4922
0
    return nullptr;
4923
0
}
4924
//! @endcond
4925
4926
// ---------------------------------------------------------------------------
4927
4928
/** \brief Returns a cs::CoordinateSystem from the specified code.
4929
 *
4930
 * @param code Object code allocated by authority.
4931
 * @return object.
4932
 * @throw NoSuchAuthorityCodeException
4933
 * @throw FactoryException
4934
 */
4935
4936
cs::CoordinateSystemNNPtr
4937
0
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
4938
0
    const auto cacheKey(d->authority() + code);
4939
0
    {
4940
0
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
4941
0
        if (cs) {
4942
0
            return NN_NO_CHECK(cs);
4943
0
        }
4944
0
    }
4945
0
    auto res = d->runWithCodeParam(
4946
0
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
4947
0
        "cs.type FROM "
4948
0
        "axis LEFT JOIN coordinate_system cs ON "
4949
0
        "axis.coordinate_system_auth_name = cs.auth_name AND "
4950
0
        "axis.coordinate_system_code = cs.code WHERE "
4951
0
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
4952
0
        "BY coordinate_system_order",
4953
0
        code);
4954
0
    if (res.empty()) {
4955
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
4956
0
                                           d->authority(), code);
4957
0
    }
4958
4959
0
    const auto &csType = res.front()[5];
4960
0
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
4961
0
    for (const auto &row : res) {
4962
0
        const auto &name = row[0];
4963
0
        const auto &abbrev = row[1];
4964
0
        const auto &orientation = row[2];
4965
0
        const auto &uom_auth_name = row[3];
4966
0
        const auto &uom_code = row[4];
4967
0
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
4968
0
            throw FactoryException("no unit of measure for an axis is only "
4969
0
                                   "supported for ordinatal CS");
4970
0
        }
4971
0
        auto uom = uom_auth_name.empty()
4972
0
                       ? common::UnitOfMeasure::NONE
4973
0
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
4974
0
        auto props =
4975
0
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
4976
0
        const cs::AxisDirection *direction =
4977
0
            cs::AxisDirection::valueOf(orientation);
4978
0
        cs::MeridianPtr meridian;
4979
0
        if (direction == nullptr) {
4980
0
            if (orientation == "Geocentre > equator/0"
4981
0
                               "\xC2\xB0"
4982
0
                               "E") {
4983
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
4984
0
            } else if (orientation == "Geocentre > equator/90"
4985
0
                                      "\xC2\xB0"
4986
0
                                      "E") {
4987
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
4988
0
            } else if (orientation == "Geocentre > north pole") {
4989
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
4990
0
            } else if (starts_with(orientation, "North along ")) {
4991
0
                direction = &(cs::AxisDirection::NORTH);
4992
0
                meridian =
4993
0
                    createMeridian(orientation.substr(strlen("North along ")));
4994
0
            } else if (starts_with(orientation, "South along ")) {
4995
0
                direction = &(cs::AxisDirection::SOUTH);
4996
0
                meridian =
4997
0
                    createMeridian(orientation.substr(strlen("South along ")));
4998
0
            } else {
4999
0
                throw FactoryException("unknown axis direction: " +
5000
0
                                       orientation);
5001
0
            }
5002
0
        }
5003
0
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5004
0
            props, abbrev, *direction, uom, meridian));
5005
0
    }
5006
5007
0
    const auto cacheAndRet = [this,
5008
0
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5009
0
        d->context()->d->cache(cacheKey, cs);
5010
0
        return cs;
5011
0
    };
5012
5013
0
    auto props = util::PropertyMap()
5014
0
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5015
0
                     .set(metadata::Identifier::CODE_KEY, code);
5016
0
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5017
0
        if (axisList.size() == 2) {
5018
0
            return cacheAndRet(
5019
0
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5020
0
        }
5021
0
        if (axisList.size() == 3) {
5022
0
            return cacheAndRet(cs::EllipsoidalCS::create(
5023
0
                props, axisList[0], axisList[1], axisList[2]));
5024
0
        }
5025
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5026
0
    }
5027
0
    if (csType == CS_TYPE_CARTESIAN) {
5028
0
        if (axisList.size() == 2) {
5029
0
            return cacheAndRet(
5030
0
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5031
0
        }
5032
0
        if (axisList.size() == 3) {
5033
0
            return cacheAndRet(cs::CartesianCS::create(
5034
0
                props, axisList[0], axisList[1], axisList[2]));
5035
0
        }
5036
0
        throw FactoryException("invalid number of axis for CartesianCS");
5037
0
    }
5038
0
    if (csType == CS_TYPE_SPHERICAL) {
5039
0
        if (axisList.size() == 2) {
5040
0
            return cacheAndRet(
5041
0
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5042
0
        }
5043
0
        if (axisList.size() == 3) {
5044
0
            return cacheAndRet(cs::SphericalCS::create(
5045
0
                props, axisList[0], axisList[1], axisList[2]));
5046
0
        }
5047
0
        throw FactoryException("invalid number of axis for SphericalCS");
5048
0
    }
5049
0
    if (csType == CS_TYPE_VERTICAL) {
5050
0
        if (axisList.size() == 1) {
5051
0
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5052
0
        }
5053
0
        throw FactoryException("invalid number of axis for VerticalCS");
5054
0
    }
5055
0
    if (csType == CS_TYPE_ORDINAL) {
5056
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5057
0
    }
5058
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5059
0
}
5060
5061
// ---------------------------------------------------------------------------
5062
5063
/** \brief Returns a crs::GeodeticCRS from the specified code.
5064
 *
5065
 * @param code Object code allocated by authority.
5066
 * @return object.
5067
 * @throw NoSuchAuthorityCodeException
5068
 * @throw FactoryException
5069
 */
5070
5071
crs::GeodeticCRSNNPtr
5072
0
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5073
0
    return createGeodeticCRS(code, false);
5074
0
}
5075
5076
// ---------------------------------------------------------------------------
5077
5078
/** \brief Returns a crs::GeographicCRS from the specified code.
5079
 *
5080
 * @param code Object code allocated by authority.
5081
 * @return object.
5082
 * @throw NoSuchAuthorityCodeException
5083
 * @throw FactoryException
5084
 */
5085
5086
crs::GeographicCRSNNPtr
5087
0
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5088
0
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5089
0
        createGeodeticCRS(code, true)));
5090
0
    if (!crs) {
5091
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5092
0
                                           d->authority(), code);
5093
0
    }
5094
0
    return NN_NO_CHECK(crs);
5095
0
}
5096
5097
// ---------------------------------------------------------------------------
5098
5099
static crs::GeodeticCRSNNPtr
5100
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5101
0
               const util::PropertyMap &props) {
5102
0
    auto cs = geodCRS->coordinateSystem();
5103
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5104
0
    if (ellipsoidalCS) {
5105
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5106
0
                                          geodCRS->datumEnsemble(),
5107
0
                                          NN_NO_CHECK(ellipsoidalCS));
5108
0
    }
5109
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5110
0
    if (geocentricCS) {
5111
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5112
0
                                        geodCRS->datumEnsemble(),
5113
0
                                        NN_NO_CHECK(geocentricCS));
5114
0
    }
5115
0
    return geodCRS;
5116
0
}
5117
5118
// ---------------------------------------------------------------------------
5119
5120
crs::GeodeticCRSNNPtr
5121
AuthorityFactory::createGeodeticCRS(const std::string &code,
5122
0
                                    bool geographicOnly) const {
5123
0
    const auto cacheKey(d->authority() + code);
5124
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5125
0
    if (crs) {
5126
0
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5127
0
        if (geogCRS) {
5128
0
            return NN_NO_CHECK(geogCRS);
5129
0
        }
5130
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5131
0
                                           d->authority(), code);
5132
0
    }
5133
0
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5134
0
                    "coordinate_system_code, datum_auth_name, datum_code, "
5135
0
                    "text_definition, deprecated, description FROM "
5136
0
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5137
0
    if (geographicOnly) {
5138
0
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5139
0
               ")";
5140
0
    }
5141
0
    auto res = d->runWithCodeParam(sql, code);
5142
0
    if (res.empty()) {
5143
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5144
0
                                           d->authority(), code);
5145
0
    }
5146
0
    try {
5147
0
        const auto &row = res.front();
5148
0
        const auto &name = row[0];
5149
0
        const auto &type = row[1];
5150
0
        const auto &cs_auth_name = row[2];
5151
0
        const auto &cs_code = row[3];
5152
0
        const auto &datum_auth_name = row[4];
5153
0
        const auto &datum_code = row[5];
5154
0
        const auto &text_definition = row[6];
5155
0
        const bool deprecated = row[7] == "1";
5156
0
        const auto &remarks = row[8];
5157
5158
0
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5159
0
                                                     deprecated, remarks);
5160
5161
0
        if (!text_definition.empty()) {
5162
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5163
0
            auto obj = createFromUserInput(
5164
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5165
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5166
0
            if (geodCRS) {
5167
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5168
0
                d->context()->d->cache(cacheKey, crsRet);
5169
0
                return crsRet;
5170
0
            }
5171
5172
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5173
0
            if (boundCRS) {
5174
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5175
0
                    boundCRS->baseCRS());
5176
0
                if (geodCRS) {
5177
0
                    auto newBoundCRS = crs::BoundCRS::create(
5178
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5179
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5180
0
                    return NN_NO_CHECK(
5181
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5182
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5183
0
                }
5184
0
            }
5185
5186
0
            throw FactoryException(
5187
0
                "text_definition does not define a GeodeticCRS");
5188
0
        }
5189
5190
0
        auto cs =
5191
0
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5192
0
        datum::GeodeticReferenceFramePtr datum;
5193
0
        datum::DatumEnsemblePtr datumEnsemble;
5194
0
        constexpr bool turnEnsembleAsDatum = false;
5195
0
        d->createFactory(datum_auth_name)
5196
0
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5197
0
                                            turnEnsembleAsDatum);
5198
5199
0
        auto ellipsoidalCS =
5200
0
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5201
0
        if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) {
5202
0
            auto crsRet = crs::GeographicCRS::create(
5203
0
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5204
0
            d->context()->d->cache(cacheKey, crsRet);
5205
0
            return crsRet;
5206
0
        }
5207
5208
0
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5209
0
        if (type == GEOCENTRIC && geocentricCS) {
5210
0
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5211
0
                                                   NN_NO_CHECK(geocentricCS));
5212
0
            d->context()->d->cache(cacheKey, crsRet);
5213
0
            return crsRet;
5214
0
        }
5215
5216
0
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5217
0
        if (type == OTHER && sphericalCS) {
5218
0
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5219
0
                                                   NN_NO_CHECK(sphericalCS));
5220
0
            d->context()->d->cache(cacheKey, crsRet);
5221
0
            return crsRet;
5222
0
        }
5223
5224
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5225
0
                               type + ", " + cs->getWKT2Type(true));
5226
0
    } catch (const std::exception &ex) {
5227
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5228
0
    }
5229
0
}
5230
5231
// ---------------------------------------------------------------------------
5232
5233
/** \brief Returns a crs::VerticalCRS from the specified code.
5234
 *
5235
 * @param code Object code allocated by authority.
5236
 * @return object.
5237
 * @throw NoSuchAuthorityCodeException
5238
 * @throw FactoryException
5239
 */
5240
5241
crs::VerticalCRSNNPtr
5242
0
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5243
0
    const auto cacheKey(d->authority() + code);
5244
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5245
0
    if (crs) {
5246
0
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5247
0
        if (projCRS) {
5248
0
            return NN_NO_CHECK(projCRS);
5249
0
        }
5250
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5251
0
                                           d->authority(), code);
5252
0
    }
5253
0
    auto res = d->runWithCodeParam(
5254
0
        "SELECT name, coordinate_system_auth_name, "
5255
0
        "coordinate_system_code, datum_auth_name, datum_code, "
5256
0
        "deprecated FROM "
5257
0
        "vertical_crs WHERE auth_name = ? AND code = ?",
5258
0
        code);
5259
0
    if (res.empty()) {
5260
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5261
0
                                           d->authority(), code);
5262
0
    }
5263
0
    try {
5264
0
        const auto &row = res.front();
5265
0
        const auto &name = row[0];
5266
0
        const auto &cs_auth_name = row[1];
5267
0
        const auto &cs_code = row[2];
5268
0
        const auto &datum_auth_name = row[3];
5269
0
        const auto &datum_code = row[4];
5270
0
        const bool deprecated = row[5] == "1";
5271
0
        auto cs =
5272
0
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5273
0
        datum::VerticalReferenceFramePtr datum;
5274
0
        datum::DatumEnsemblePtr datumEnsemble;
5275
0
        constexpr bool turnEnsembleAsDatum = false;
5276
0
        d->createFactory(datum_auth_name)
5277
0
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5278
0
                                            turnEnsembleAsDatum);
5279
0
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5280
0
                                                     deprecated);
5281
5282
0
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5283
0
        if (verticalCS) {
5284
0
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5285
0
                                                   NN_NO_CHECK(verticalCS));
5286
0
            d->context()->d->cache(cacheKey, crsRet);
5287
0
            return crsRet;
5288
0
        }
5289
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5290
0
                               cs->getWKT2Type(true));
5291
0
    } catch (const std::exception &ex) {
5292
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5293
0
    }
5294
0
}
5295
5296
// ---------------------------------------------------------------------------
5297
5298
/** \brief Returns a operation::Conversion from the specified code.
5299
 *
5300
 * @param code Object code allocated by authority.
5301
 * @return object.
5302
 * @throw NoSuchAuthorityCodeException
5303
 * @throw FactoryException
5304
 */
5305
5306
operation::ConversionNNPtr
5307
0
AuthorityFactory::createConversion(const std::string &code) const {
5308
5309
0
    static const char *sql =
5310
0
        "SELECT name, description, "
5311
0
        "method_auth_name, method_code, method_name, "
5312
5313
0
        "param1_auth_name, param1_code, param1_name, param1_value, "
5314
0
        "param1_uom_auth_name, param1_uom_code, "
5315
5316
0
        "param2_auth_name, param2_code, param2_name, param2_value, "
5317
0
        "param2_uom_auth_name, param2_uom_code, "
5318
5319
0
        "param3_auth_name, param3_code, param3_name, param3_value, "
5320
0
        "param3_uom_auth_name, param3_uom_code, "
5321
5322
0
        "param4_auth_name, param4_code, param4_name, param4_value, "
5323
0
        "param4_uom_auth_name, param4_uom_code, "
5324
5325
0
        "param5_auth_name, param5_code, param5_name, param5_value, "
5326
0
        "param5_uom_auth_name, param5_uom_code, "
5327
5328
0
        "param6_auth_name, param6_code, param6_name, param6_value, "
5329
0
        "param6_uom_auth_name, param6_uom_code, "
5330
5331
0
        "param7_auth_name, param7_code, param7_name, param7_value, "
5332
0
        "param7_uom_auth_name, param7_uom_code, "
5333
5334
0
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5335
5336
0
    auto res = d->runWithCodeParam(sql, code);
5337
0
    if (res.empty()) {
5338
0
        try {
5339
            // Conversions using methods Change of Vertical Unit or
5340
            // Height Depth Reversal are stored in other_transformation
5341
0
            auto op = createCoordinateOperation(
5342
0
                code, false /* allowConcatenated */,
5343
0
                false /* usePROJAlternativeGridNames */,
5344
0
                "other_transformation");
5345
0
            auto conv =
5346
0
                util::nn_dynamic_pointer_cast<operation::Conversion>(op);
5347
0
            if (conv) {
5348
0
                return NN_NO_CHECK(conv);
5349
0
            }
5350
0
        } catch (const std::exception &) {
5351
0
        }
5352
0
        throw NoSuchAuthorityCodeException("conversion not found",
5353
0
                                           d->authority(), code);
5354
0
    }
5355
0
    try {
5356
0
        const auto &row = res.front();
5357
0
        size_t idx = 0;
5358
0
        const auto &name = row[idx++];
5359
0
        const auto &description = row[idx++];
5360
0
        const auto &method_auth_name = row[idx++];
5361
0
        const auto &method_code = row[idx++];
5362
0
        const auto &method_name = row[idx++];
5363
0
        const size_t base_param_idx = idx;
5364
0
        std::vector<operation::OperationParameterNNPtr> parameters;
5365
0
        std::vector<operation::ParameterValueNNPtr> values;
5366
0
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5367
0
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5368
0
            if (param_auth_name.empty()) {
5369
0
                break;
5370
0
            }
5371
0
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5372
0
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5373
0
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5374
0
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5375
0
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5376
0
            parameters.emplace_back(operation::OperationParameter::create(
5377
0
                util::PropertyMap()
5378
0
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5379
0
                    .set(metadata::Identifier::CODE_KEY, param_code)
5380
0
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5381
0
            std::string normalized_uom_code(param_uom_code);
5382
0
            const double normalized_value = normalizeMeasure(
5383
0
                param_uom_code, param_value, normalized_uom_code);
5384
0
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5385
0
                                              normalized_uom_code);
5386
0
            values.emplace_back(operation::ParameterValue::create(
5387
0
                common::Measure(normalized_value, uom)));
5388
0
        }
5389
0
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5390
5391
0
        auto propConversion = d->createPropertiesSearchUsages(
5392
0
            "conversion", code, name, deprecated);
5393
0
        if (!description.empty())
5394
0
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5395
0
                               description);
5396
5397
0
        auto propMethod = util::PropertyMap().set(
5398
0
            common::IdentifiedObject::NAME_KEY, method_name);
5399
0
        if (!method_auth_name.empty()) {
5400
0
            propMethod
5401
0
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5402
0
                .set(metadata::Identifier::CODE_KEY, method_code);
5403
0
        }
5404
5405
0
        return operation::Conversion::create(propConversion, propMethod,
5406
0
                                             parameters, values);
5407
0
    } catch (const std::exception &ex) {
5408
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5409
0
    }
5410
0
}
5411
5412
// ---------------------------------------------------------------------------
5413
5414
/** \brief Returns a crs::ProjectedCRS from the specified code.
5415
 *
5416
 * @param code Object code allocated by authority.
5417
 * @return object.
5418
 * @throw NoSuchAuthorityCodeException
5419
 * @throw FactoryException
5420
 */
5421
5422
crs::ProjectedCRSNNPtr
5423
0
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5424
0
    const auto cacheKey(d->authority() + code);
5425
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5426
0
    if (crs) {
5427
0
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5428
0
        if (projCRS) {
5429
0
            return NN_NO_CHECK(projCRS);
5430
0
        }
5431
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5432
0
                                           d->authority(), code);
5433
0
    }
5434
0
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5435
0
}
5436
5437
// ---------------------------------------------------------------------------
5438
//! @cond Doxygen_Suppress
5439
5440
/** Returns the result of the SQL query needed by createProjectedCRSEnd
5441
 *
5442
 * The split in two functions is for createFromCoordinateReferenceSystemCodes()
5443
 * convenience, to avoid throwing exceptions.
5444
 */
5445
SQLResultSet
5446
0
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5447
0
    return runWithCodeParam(
5448
0
        "SELECT name, coordinate_system_auth_name, "
5449
0
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5450
0
        "conversion_auth_name, conversion_code, "
5451
0
        "text_definition, "
5452
0
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5453
0
        code);
5454
0
}
5455
5456
// ---------------------------------------------------------------------------
5457
5458
/** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
5459
crs::ProjectedCRSNNPtr
5460
AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
5461
0
                                                 const SQLResultSet &res) {
5462
0
    const auto cacheKey(authority() + code);
5463
0
    if (res.empty()) {
5464
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5465
0
                                           authority(), code);
5466
0
    }
5467
0
    try {
5468
0
        const auto &row = res.front();
5469
0
        const auto &name = row[0];
5470
0
        const auto &cs_auth_name = row[1];
5471
0
        const auto &cs_code = row[2];
5472
0
        const auto &geodetic_crs_auth_name = row[3];
5473
0
        const auto &geodetic_crs_code = row[4];
5474
0
        const auto &conversion_auth_name = row[5];
5475
0
        const auto &conversion_code = row[6];
5476
0
        const auto &text_definition = row[7];
5477
0
        const bool deprecated = row[8] == "1";
5478
5479
0
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5480
0
                                                  deprecated);
5481
5482
0
        if (!text_definition.empty()) {
5483
0
            DatabaseContext::Private::RecursionDetector detector(context());
5484
0
            auto obj = createFromUserInput(
5485
0
                pj_add_type_crs_if_needed(text_definition), context());
5486
0
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5487
0
            if (projCRS) {
5488
0
                const auto conv = projCRS->derivingConversion();
5489
0
                auto newConv =
5490
0
                    (conv->nameStr() == "unnamed")
5491
0
                        ? operation::Conversion::create(
5492
0
                              util::PropertyMap().set(
5493
0
                                  common::IdentifiedObject::NAME_KEY, name),
5494
0
                              conv->method(), conv->parameterValues())
5495
0
                        : conv;
5496
0
                auto crsRet = crs::ProjectedCRS::create(
5497
0
                    props, projCRS->baseCRS(), newConv,
5498
0
                    projCRS->coordinateSystem());
5499
0
                context()->d->cache(cacheKey, crsRet);
5500
0
                return crsRet;
5501
0
            }
5502
5503
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5504
0
            if (boundCRS) {
5505
0
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
5506
0
                    boundCRS->baseCRS().get());
5507
0
                if (projCRS) {
5508
0
                    auto newBoundCRS = crs::BoundCRS::create(
5509
0
                        crs::ProjectedCRS::create(props, projCRS->baseCRS(),
5510
0
                                                  projCRS->derivingConversion(),
5511
0
                                                  projCRS->coordinateSystem()),
5512
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5513
0
                    return NN_NO_CHECK(
5514
0
                        util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
5515
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5516
0
                }
5517
0
            }
5518
5519
0
            throw FactoryException(
5520
0
                "text_definition does not define a ProjectedCRS");
5521
0
        }
5522
5523
0
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5524
5525
0
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5526
0
                           ->createGeodeticCRS(geodetic_crs_code);
5527
5528
0
        auto conv = createFactory(conversion_auth_name)
5529
0
                        ->createConversion(conversion_code);
5530
0
        if (conv->nameStr() == "unnamed") {
5531
0
            conv = conv->shallowClone();
5532
0
            conv->setProperties(util::PropertyMap().set(
5533
0
                common::IdentifiedObject::NAME_KEY, name));
5534
0
        }
5535
5536
0
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5537
0
        if (cartesianCS) {
5538
0
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5539
0
                                                    NN_NO_CHECK(cartesianCS));
5540
0
            context()->d->cache(cacheKey, crsRet);
5541
0
            return crsRet;
5542
0
        }
5543
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5544
0
                               cs->getWKT2Type(true));
5545
0
    } catch (const std::exception &ex) {
5546
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5547
0
    }
5548
0
}
5549
//! @endcond
5550
5551
// ---------------------------------------------------------------------------
5552
5553
/** \brief Returns a crs::CompoundCRS from the specified code.
5554
 *
5555
 * @param code Object code allocated by authority.
5556
 * @return object.
5557
 * @throw NoSuchAuthorityCodeException
5558
 * @throw FactoryException
5559
 */
5560
5561
crs::CompoundCRSNNPtr
5562
0
AuthorityFactory::createCompoundCRS(const std::string &code) const {
5563
0
    auto res =
5564
0
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
5565
0
                            "vertical_crs_auth_name, vertical_crs_code, "
5566
0
                            "deprecated FROM "
5567
0
                            "compound_crs WHERE auth_name = ? AND code = ?",
5568
0
                            code);
5569
0
    if (res.empty()) {
5570
0
        throw NoSuchAuthorityCodeException("compoundCRS not found",
5571
0
                                           d->authority(), code);
5572
0
    }
5573
0
    try {
5574
0
        const auto &row = res.front();
5575
0
        const auto &name = row[0];
5576
0
        const auto &horiz_crs_auth_name = row[1];
5577
0
        const auto &horiz_crs_code = row[2];
5578
0
        const auto &vertical_crs_auth_name = row[3];
5579
0
        const auto &vertical_crs_code = row[4];
5580
0
        const bool deprecated = row[5] == "1";
5581
5582
0
        auto horizCRS =
5583
0
            d->createFactory(horiz_crs_auth_name)
5584
0
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
5585
0
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
5586
0
                           ->createVerticalCRS(vertical_crs_code);
5587
5588
0
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
5589
0
                                                     deprecated);
5590
0
        return crs::CompoundCRS::create(
5591
0
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
5592
0
                                              std::move(vertCRS)});
5593
0
    } catch (const std::exception &ex) {
5594
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
5595
0
    }
5596
0
}
5597
5598
// ---------------------------------------------------------------------------
5599
5600
/** \brief Returns a crs::CRS from the specified code.
5601
 *
5602
 * @param code Object code allocated by authority.
5603
 * @return object.
5604
 * @throw NoSuchAuthorityCodeException
5605
 * @throw FactoryException
5606
 */
5607
5608
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
5609
0
    const std::string &code) const {
5610
0
    return createCoordinateReferenceSystem(code, true);
5611
0
}
5612
5613
//! @cond Doxygen_Suppress
5614
5615
crs::CRSNNPtr
5616
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
5617
0
                                                  bool allowCompound) const {
5618
0
    const auto cacheKey(d->authority() + code);
5619
0
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5620
0
    if (crs) {
5621
0
        return NN_NO_CHECK(crs);
5622
0
    }
5623
5624
0
    if (d->authority() == metadata::Identifier::OGC) {
5625
0
        if (code == "AnsiDate") {
5626
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
5627
0
            return crs::TemporalCRS::create(
5628
0
                util::PropertyMap()
5629
                    // above URL indicates Julian Date" as name... likely wrong
5630
0
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
5631
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5632
0
                    .set(metadata::Identifier::CODE_KEY, code),
5633
0
                datum::TemporalDatum::create(
5634
0
                    util::PropertyMap().set(
5635
0
                        common::IdentifiedObject::NAME_KEY,
5636
0
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
5637
0
                        "as day 1."),
5638
0
                    common::DateTime::create("1600-12-31T00:00:00Z"),
5639
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
5640
0
                cs::TemporalCountCS::create(
5641
0
                    util::PropertyMap(),
5642
0
                    cs::CoordinateSystemAxis::create(
5643
0
                        util::PropertyMap().set(
5644
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
5645
0
                        "T", cs::AxisDirection::FUTURE,
5646
0
                        common::UnitOfMeasure("day", 0,
5647
0
                                              UnitOfMeasure::Type::TIME))));
5648
0
        }
5649
0
        if (code == "JulianDate") {
5650
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
5651
0
            return crs::TemporalCRS::create(
5652
0
                util::PropertyMap()
5653
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
5654
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5655
0
                    .set(metadata::Identifier::CODE_KEY, code),
5656
0
                datum::TemporalDatum::create(
5657
0
                    util::PropertyMap().set(
5658
0
                        common::IdentifiedObject::NAME_KEY,
5659
0
                        "The beginning of the Julian period."),
5660
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
5661
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
5662
0
                cs::TemporalCountCS::create(
5663
0
                    util::PropertyMap(),
5664
0
                    cs::CoordinateSystemAxis::create(
5665
0
                        util::PropertyMap().set(
5666
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
5667
0
                        "T", cs::AxisDirection::FUTURE,
5668
0
                        common::UnitOfMeasure("day", 0,
5669
0
                                              UnitOfMeasure::Type::TIME))));
5670
0
        }
5671
0
        if (code == "UnixTime") {
5672
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
5673
0
            return crs::TemporalCRS::create(
5674
0
                util::PropertyMap()
5675
0
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
5676
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5677
0
                    .set(metadata::Identifier::CODE_KEY, code),
5678
0
                datum::TemporalDatum::create(
5679
0
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
5680
0
                                            "Unix epoch"),
5681
0
                    common::DateTime::create("1970-01-01T00:00:00Z"),
5682
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
5683
0
                cs::TemporalCountCS::create(
5684
0
                    util::PropertyMap(),
5685
0
                    cs::CoordinateSystemAxis::create(
5686
0
                        util::PropertyMap().set(
5687
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
5688
0
                        "T", cs::AxisDirection::FUTURE,
5689
0
                        common::UnitOfMeasure::SECOND)));
5690
0
        }
5691
0
        if (code == "84") {
5692
0
            return createCoordinateReferenceSystem("CRS84", false);
5693
0
        }
5694
0
    }
5695
5696
0
    auto res = d->runWithCodeParam(
5697
0
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
5698
0
    if (res.empty()) {
5699
0
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
5700
0
                                           code);
5701
0
    }
5702
0
    const auto &type = res.front()[0];
5703
0
    if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC ||
5704
0
        type == OTHER) {
5705
0
        return createGeodeticCRS(code);
5706
0
    }
5707
0
    if (type == VERTICAL) {
5708
0
        return createVerticalCRS(code);
5709
0
    }
5710
0
    if (type == PROJECTED) {
5711
0
        return createProjectedCRS(code);
5712
0
    }
5713
0
    if (allowCompound && type == COMPOUND) {
5714
0
        return createCompoundCRS(code);
5715
0
    }
5716
0
    throw FactoryException("unhandled CRS type: " + type);
5717
0
}
5718
5719
//! @endcond
5720
5721
// ---------------------------------------------------------------------------
5722
5723
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
5724
 *
5725
 * @param code Object code allocated by authority.
5726
 * @return object.
5727
 * @throw NoSuchAuthorityCodeException
5728
 * @throw FactoryException
5729
 * @since 9.4
5730
 */
5731
5732
coordinates::CoordinateMetadataNNPtr
5733
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
5734
0
    auto res = d->runWithCodeParam(
5735
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
5736
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
5737
0
        code);
5738
0
    if (res.empty()) {
5739
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
5740
0
                                           d->authority(), code);
5741
0
    }
5742
0
    try {
5743
0
        const auto &row = res.front();
5744
0
        const auto &crs_auth_name = row[0];
5745
0
        const auto &crs_code = row[1];
5746
0
        const auto &crs_text_definition = row[2];
5747
0
        const auto &coordinate_epoch = row[3];
5748
5749
0
        auto l_context = d->context();
5750
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
5751
0
        auto crs =
5752
0
            !crs_auth_name.empty()
5753
0
                ? d->createFactory(crs_auth_name)
5754
0
                      ->createCoordinateReferenceSystem(crs_code)
5755
0
                      .as_nullable()
5756
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
5757
0
                      createFromUserInput(crs_text_definition, l_context));
5758
0
        if (!crs) {
5759
0
            throw FactoryException(
5760
0
                std::string("cannot build CoordinateMetadata ") +
5761
0
                d->authority() + ":" + code + ": cannot build CRS");
5762
0
        }
5763
0
        if (coordinate_epoch.empty()) {
5764
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
5765
0
        } else {
5766
0
            return coordinates::CoordinateMetadata::create(
5767
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
5768
0
                l_context.as_nullable());
5769
0
        }
5770
0
    } catch (const std::exception &ex) {
5771
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
5772
0
                                    ex);
5773
0
    }
5774
0
}
5775
5776
// ---------------------------------------------------------------------------
5777
5778
//! @cond Doxygen_Suppress
5779
5780
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
5781
0
                                               int code) {
5782
0
    return util::PropertyMap()
5783
0
        .set(common::IdentifiedObject::NAME_KEY, name)
5784
0
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
5785
0
        .set(metadata::Identifier::CODE_KEY, code);
5786
0
}
5787
5788
// ---------------------------------------------------------------------------
5789
5790
0
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
5791
0
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
5792
0
    assert(name);
5793
0
    return operation::OperationParameter::create(
5794
0
        createMapNameEPSGCode(name, code));
5795
0
}
5796
5797
static operation::ParameterValueNNPtr createLength(const std::string &value,
5798
0
                                                   const UnitOfMeasure &uom) {
5799
0
    return operation::ParameterValue::create(
5800
0
        common::Length(c_locale_stod(value), uom));
5801
0
}
5802
5803
static operation::ParameterValueNNPtr createAngle(const std::string &value,
5804
0
                                                  const UnitOfMeasure &uom) {
5805
0
    return operation::ParameterValue::create(
5806
0
        common::Angle(c_locale_stod(value), uom));
5807
0
}
5808
5809
//! @endcond
5810
5811
// ---------------------------------------------------------------------------
5812
5813
/** \brief Returns a operation::CoordinateOperation from the specified code.
5814
 *
5815
 * @param code Object code allocated by authority.
5816
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
5817
 * should be substituted to the official grid names.
5818
 * @return object.
5819
 * @throw NoSuchAuthorityCodeException
5820
 * @throw FactoryException
5821
 */
5822
5823
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
5824
0
    const std::string &code, bool usePROJAlternativeGridNames) const {
5825
0
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
5826
0
                                     std::string());
5827
0
}
5828
5829
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
5830
    const std::string &code, bool allowConcatenated,
5831
0
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
5832
0
    std::string type(typeIn);
5833
0
    if (type.empty()) {
5834
0
        auto res = d->runWithCodeParam(
5835
0
            "SELECT type FROM coordinate_operation_with_conversion_view "
5836
0
            "WHERE auth_name = ? AND code = ?",
5837
0
            code);
5838
0
        if (res.empty()) {
5839
0
            throw NoSuchAuthorityCodeException("coordinate operation not found",
5840
0
                                               d->authority(), code);
5841
0
        }
5842
0
        type = res.front()[0];
5843
0
    }
5844
5845
0
    if (type == "conversion") {
5846
0
        return createConversion(code);
5847
0
    }
5848
5849
0
    if (type == "helmert_transformation") {
5850
5851
0
        auto res = d->runWithCodeParam(
5852
0
            "SELECT name, description, "
5853
0
            "method_auth_name, method_code, method_name, "
5854
0
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
5855
0
            "target_crs_code, "
5856
0
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
5857
0
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
5858
0
            "rotation_uom_code, scale_difference, "
5859
0
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
5860
0
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
5861
0
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
5862
0
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
5863
0
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
5864
0
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
5865
0
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
5866
0
            "operation_version, deprecated FROM "
5867
0
            "helmert_transformation WHERE auth_name = ? AND code = ?",
5868
0
            code);
5869
0
        if (res.empty()) {
5870
            // shouldn't happen if foreign keys are OK
5871
0
            throw NoSuchAuthorityCodeException(
5872
0
                "helmert_transformation not found", d->authority(), code);
5873
0
        }
5874
0
        try {
5875
0
            const auto &row = res.front();
5876
0
            size_t idx = 0;
5877
0
            const auto &name = row[idx++];
5878
0
            const auto &description = row[idx++];
5879
0
            const auto &method_auth_name = row[idx++];
5880
0
            const auto &method_code = row[idx++];
5881
0
            const auto &method_name = row[idx++];
5882
0
            const auto &source_crs_auth_name = row[idx++];
5883
0
            const auto &source_crs_code = row[idx++];
5884
0
            const auto &target_crs_auth_name = row[idx++];
5885
0
            const auto &target_crs_code = row[idx++];
5886
0
            const auto &accuracy = row[idx++];
5887
5888
0
            const auto &tx = row[idx++];
5889
0
            const auto &ty = row[idx++];
5890
0
            const auto &tz = row[idx++];
5891
0
            const auto &translation_uom_auth_name = row[idx++];
5892
0
            const auto &translation_uom_code = row[idx++];
5893
0
            const auto &rx = row[idx++];
5894
0
            const auto &ry = row[idx++];
5895
0
            const auto &rz = row[idx++];
5896
0
            const auto &rotation_uom_auth_name = row[idx++];
5897
0
            const auto &rotation_uom_code = row[idx++];
5898
0
            const auto &scale_difference = row[idx++];
5899
0
            const auto &scale_difference_uom_auth_name = row[idx++];
5900
0
            const auto &scale_difference_uom_code = row[idx++];
5901
5902
0
            const auto &rate_tx = row[idx++];
5903
0
            const auto &rate_ty = row[idx++];
5904
0
            const auto &rate_tz = row[idx++];
5905
0
            const auto &rate_translation_uom_auth_name = row[idx++];
5906
0
            const auto &rate_translation_uom_code = row[idx++];
5907
0
            const auto &rate_rx = row[idx++];
5908
0
            const auto &rate_ry = row[idx++];
5909
0
            const auto &rate_rz = row[idx++];
5910
0
            const auto &rate_rotation_uom_auth_name = row[idx++];
5911
0
            const auto &rate_rotation_uom_code = row[idx++];
5912
0
            const auto &rate_scale_difference = row[idx++];
5913
0
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
5914
0
            const auto &rate_scale_difference_uom_code = row[idx++];
5915
5916
0
            const auto &epoch = row[idx++];
5917
0
            const auto &epoch_uom_auth_name = row[idx++];
5918
0
            const auto &epoch_uom_code = row[idx++];
5919
5920
0
            const auto &px = row[idx++];
5921
0
            const auto &py = row[idx++];
5922
0
            const auto &pz = row[idx++];
5923
0
            const auto &pivot_uom_auth_name = row[idx++];
5924
0
            const auto &pivot_uom_code = row[idx++];
5925
5926
0
            const auto &operation_version = row[idx++];
5927
0
            const auto &deprecated_str = row[idx++];
5928
0
            const bool deprecated = deprecated_str == "1";
5929
0
            assert(idx == row.size());
5930
5931
0
            auto uom_translation = d->createUnitOfMeasure(
5932
0
                translation_uom_auth_name, translation_uom_code);
5933
5934
0
            auto uom_epoch = epoch_uom_auth_name.empty()
5935
0
                                 ? common::UnitOfMeasure::NONE
5936
0
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
5937
0
                                                          epoch_uom_code);
5938
5939
0
            auto sourceCRS =
5940
0
                d->createFactory(source_crs_auth_name)
5941
0
                    ->createCoordinateReferenceSystem(source_crs_code);
5942
0
            auto targetCRS =
5943
0
                d->createFactory(target_crs_auth_name)
5944
0
                    ->createCoordinateReferenceSystem(target_crs_code);
5945
5946
0
            std::vector<operation::OperationParameterNNPtr> parameters;
5947
0
            std::vector<operation::ParameterValueNNPtr> values;
5948
5949
0
            parameters.emplace_back(createOpParamNameEPSGCode(
5950
0
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
5951
0
            values.emplace_back(createLength(tx, uom_translation));
5952
5953
0
            parameters.emplace_back(createOpParamNameEPSGCode(
5954
0
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
5955
0
            values.emplace_back(createLength(ty, uom_translation));
5956
5957
0
            parameters.emplace_back(createOpParamNameEPSGCode(
5958
0
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
5959
0
            values.emplace_back(createLength(tz, uom_translation));
5960
5961
0
            if (!rx.empty()) {
5962
                // Helmert 7-, 8-, 10- or 15- parameter cases
5963
0
                auto uom_rotation = d->createUnitOfMeasure(
5964
0
                    rotation_uom_auth_name, rotation_uom_code);
5965
5966
0
                parameters.emplace_back(createOpParamNameEPSGCode(
5967
0
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
5968
0
                values.emplace_back(createAngle(rx, uom_rotation));
5969
5970
0
                parameters.emplace_back(createOpParamNameEPSGCode(
5971
0
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
5972
0
                values.emplace_back(createAngle(ry, uom_rotation));
5973
5974
0
                parameters.emplace_back(createOpParamNameEPSGCode(
5975
0
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
5976
0
                values.emplace_back(createAngle(rz, uom_rotation));
5977
5978
0
                auto uom_scale_difference =
5979
0
                    scale_difference_uom_auth_name.empty()
5980
0
                        ? common::UnitOfMeasure::NONE
5981
0
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
5982
0
                                                 scale_difference_uom_code);
5983
5984
0
                parameters.emplace_back(createOpParamNameEPSGCode(
5985
0
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
5986
0
                values.emplace_back(operation::ParameterValue::create(
5987
0
                    common::Scale(c_locale_stod(scale_difference),
5988
0
                                  uom_scale_difference)));
5989
0
            }
5990
5991
0
            if (!rate_tx.empty()) {
5992
                // Helmert 15-parameter
5993
5994
0
                auto uom_rate_translation = d->createUnitOfMeasure(
5995
0
                    rate_translation_uom_auth_name, rate_translation_uom_code);
5996
5997
0
                parameters.emplace_back(createOpParamNameEPSGCode(
5998
0
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
5999
0
                values.emplace_back(
6000
0
                    createLength(rate_tx, uom_rate_translation));
6001
6002
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6003
0
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6004
0
                values.emplace_back(
6005
0
                    createLength(rate_ty, uom_rate_translation));
6006
6007
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6008
0
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6009
0
                values.emplace_back(
6010
0
                    createLength(rate_tz, uom_rate_translation));
6011
6012
0
                auto uom_rate_rotation = d->createUnitOfMeasure(
6013
0
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6014
6015
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6016
0
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6017
0
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6018
6019
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6020
0
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6021
0
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6022
6023
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6024
0
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6025
0
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6026
6027
0
                auto uom_rate_scale_difference =
6028
0
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6029
0
                                           rate_scale_difference_uom_code);
6030
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6031
0
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6032
0
                values.emplace_back(operation::ParameterValue::create(
6033
0
                    common::Scale(c_locale_stod(rate_scale_difference),
6034
0
                                  uom_rate_scale_difference)));
6035
6036
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6037
0
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6038
0
                values.emplace_back(operation::ParameterValue::create(
6039
0
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6040
0
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6041
                // Helmert 8-parameter
6042
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6043
0
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6044
0
                values.emplace_back(operation::ParameterValue::create(
6045
0
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6046
0
            } else if (!px.empty()) {
6047
                // Molodensky-Badekas case
6048
0
                auto uom_pivot =
6049
0
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6050
6051
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6052
0
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6053
0
                values.emplace_back(createLength(px, uom_pivot));
6054
6055
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6056
0
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6057
0
                values.emplace_back(createLength(py, uom_pivot));
6058
6059
0
                parameters.emplace_back(createOpParamNameEPSGCode(
6060
0
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6061
0
                values.emplace_back(createLength(pz, uom_pivot));
6062
0
            }
6063
6064
0
            auto props = d->createPropertiesSearchUsages(
6065
0
                type, code, name, deprecated, description);
6066
0
            if (!operation_version.empty()) {
6067
0
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6068
0
                          operation_version);
6069
0
            }
6070
6071
0
            auto propsMethod =
6072
0
                util::PropertyMap()
6073
0
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6074
0
                    .set(metadata::Identifier::CODE_KEY, method_code)
6075
0
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6076
6077
0
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6078
0
            if (!accuracy.empty()) {
6079
0
                accuracies.emplace_back(
6080
0
                    metadata::PositionalAccuracy::create(accuracy));
6081
0
            }
6082
0
            return operation::Transformation::create(
6083
0
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6084
0
                values, accuracies);
6085
6086
0
        } catch (const std::exception &ex) {
6087
0
            throw buildFactoryException("transformation", d->authority(), code,
6088
0
                                        ex);
6089
0
        }
6090
0
    }
6091
6092
0
    if (type == "grid_transformation") {
6093
0
        auto res = d->runWithCodeParam(
6094
0
            "SELECT name, description, "
6095
0
            "method_auth_name, method_code, method_name, "
6096
0
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6097
0
            "target_crs_code, "
6098
0
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6099
0
            "grid_name, "
6100
0
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6101
0
            "grid2_name, "
6102
0
            "interpolation_crs_auth_name, interpolation_crs_code, "
6103
0
            "operation_version, deprecated FROM "
6104
0
            "grid_transformation WHERE auth_name = ? AND code = ?",
6105
0
            code);
6106
0
        if (res.empty()) {
6107
            // shouldn't happen if foreign keys are OK
6108
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6109
0
                                               d->authority(), code);
6110
0
        }
6111
0
        try {
6112
0
            const auto &row = res.front();
6113
0
            size_t idx = 0;
6114
0
            const auto &name = row[idx++];
6115
0
            const auto &description = row[idx++];
6116
0
            const auto &method_auth_name = row[idx++];
6117
0
            const auto &method_code = row[idx++];
6118
0
            const auto &method_name = row[idx++];
6119
0
            const auto &source_crs_auth_name = row[idx++];
6120
0
            const auto &source_crs_code = row[idx++];
6121
0
            const auto &target_crs_auth_name = row[idx++];
6122
0
            const auto &target_crs_code = row[idx++];
6123
0
            const auto &accuracy = row[idx++];
6124
0
            const auto &grid_param_auth_name = row[idx++];
6125
0
            const auto &grid_param_code = row[idx++];
6126
0
            const auto &grid_param_name = row[idx++];
6127
0
            const auto &grid_name = row[idx++];
6128
0
            const auto &grid2_param_auth_name = row[idx++];
6129
0
            const auto &grid2_param_code = row[idx++];
6130
0
            const auto &grid2_param_name = row[idx++];
6131
0
            const auto &grid2_name = row[idx++];
6132
0
            const auto &interpolation_crs_auth_name = row[idx++];
6133
0
            const auto &interpolation_crs_code = row[idx++];
6134
0
            const auto &operation_version = row[idx++];
6135
0
            const auto &deprecated_str = row[idx++];
6136
0
            const bool deprecated = deprecated_str == "1";
6137
0
            assert(idx == row.size());
6138
6139
0
            auto sourceCRS =
6140
0
                d->createFactory(source_crs_auth_name)
6141
0
                    ->createCoordinateReferenceSystem(source_crs_code);
6142
0
            auto targetCRS =
6143
0
                d->createFactory(target_crs_auth_name)
6144
0
                    ->createCoordinateReferenceSystem(target_crs_code);
6145
0
            auto interpolationCRS =
6146
0
                interpolation_crs_auth_name.empty()
6147
0
                    ? nullptr
6148
0
                    : d->createFactory(interpolation_crs_auth_name)
6149
0
                          ->createCoordinateReferenceSystem(
6150
0
                              interpolation_crs_code)
6151
0
                          .as_nullable();
6152
6153
0
            std::vector<operation::OperationParameterNNPtr> parameters;
6154
0
            std::vector<operation::ParameterValueNNPtr> values;
6155
6156
0
            parameters.emplace_back(operation::OperationParameter::create(
6157
0
                util::PropertyMap()
6158
0
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6159
0
                    .set(metadata::Identifier::CODESPACE_KEY,
6160
0
                         grid_param_auth_name)
6161
0
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6162
0
            values.emplace_back(
6163
0
                operation::ParameterValue::createFilename(grid_name));
6164
0
            if (!grid2_name.empty()) {
6165
0
                parameters.emplace_back(operation::OperationParameter::create(
6166
0
                    util::PropertyMap()
6167
0
                        .set(common::IdentifiedObject::NAME_KEY,
6168
0
                             grid2_param_name)
6169
0
                        .set(metadata::Identifier::CODESPACE_KEY,
6170
0
                             grid2_param_auth_name)
6171
0
                        .set(metadata::Identifier::CODE_KEY,
6172
0
                             grid2_param_code)));
6173
0
                values.emplace_back(
6174
0
                    operation::ParameterValue::createFilename(grid2_name));
6175
0
            }
6176
6177
0
            auto props = d->createPropertiesSearchUsages(
6178
0
                type, code, name, deprecated, description);
6179
0
            if (!operation_version.empty()) {
6180
0
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6181
0
                          operation_version);
6182
0
            }
6183
0
            auto propsMethod =
6184
0
                util::PropertyMap()
6185
0
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6186
0
                    .set(metadata::Identifier::CODE_KEY, method_code)
6187
0
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6188
6189
0
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6190
0
            if (!accuracy.empty()) {
6191
0
                accuracies.emplace_back(
6192
0
                    metadata::PositionalAccuracy::create(accuracy));
6193
0
            }
6194
6195
            // A bit fragile to detect the operation type with the method name,
6196
            // but not worth changing the database model
6197
0
            if (starts_with(method_name, "Point motion")) {
6198
0
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6199
0
                    throw operation::InvalidOperation(
6200
0
                        "source_crs and target_crs should be the same for a "
6201
0
                        "PointMotionOperation");
6202
0
                }
6203
6204
0
                auto pmo = operation::PointMotionOperation::create(
6205
0
                    props, sourceCRS, propsMethod, parameters, values,
6206
0
                    accuracies);
6207
0
                if (usePROJAlternativeGridNames) {
6208
0
                    return pmo->substitutePROJAlternativeGridNames(
6209
0
                        d->context());
6210
0
                }
6211
0
                return pmo;
6212
0
            }
6213
6214
0
            auto transf = operation::Transformation::create(
6215
0
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6216
0
                parameters, values, accuracies);
6217
0
            if (usePROJAlternativeGridNames) {
6218
0
                return transf->substitutePROJAlternativeGridNames(d->context());
6219
0
            }
6220
0
            return transf;
6221
6222
0
        } catch (const std::exception &ex) {
6223
0
            throw buildFactoryException("transformation", d->authority(), code,
6224
0
                                        ex);
6225
0
        }
6226
0
    }
6227
6228
0
    if (type == "other_transformation") {
6229
0
        std::ostringstream buffer;
6230
0
        buffer.imbue(std::locale::classic());
6231
0
        buffer
6232
0
            << "SELECT name, description, "
6233
0
               "method_auth_name, method_code, method_name, "
6234
0
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6235
0
               "target_crs_code, "
6236
0
               "interpolation_crs_auth_name, interpolation_crs_code, "
6237
0
               "operation_version, accuracy, deprecated";
6238
0
        for (size_t i = 1; i <= N_MAX_PARAMS; ++i) {
6239
0
            buffer << ", param" << i << "_auth_name";
6240
0
            buffer << ", param" << i << "_code";
6241
0
            buffer << ", param" << i << "_name";
6242
0
            buffer << ", param" << i << "_value";
6243
0
            buffer << ", param" << i << "_uom_auth_name";
6244
0
            buffer << ", param" << i << "_uom_code";
6245
0
        }
6246
0
        buffer << " FROM other_transformation "
6247
0
                  "WHERE auth_name = ? AND code = ?";
6248
6249
0
        auto res = d->runWithCodeParam(buffer.str(), code);
6250
0
        if (res.empty()) {
6251
            // shouldn't happen if foreign keys are OK
6252
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6253
0
                                               d->authority(), code);
6254
0
        }
6255
0
        try {
6256
0
            const auto &row = res.front();
6257
0
            size_t idx = 0;
6258
0
            const auto &name = row[idx++];
6259
0
            const auto &description = row[idx++];
6260
0
            const auto &method_auth_name = row[idx++];
6261
0
            const auto &method_code = row[idx++];
6262
0
            const auto &method_name = row[idx++];
6263
0
            const auto &source_crs_auth_name = row[idx++];
6264
0
            const auto &source_crs_code = row[idx++];
6265
0
            const auto &target_crs_auth_name = row[idx++];
6266
0
            const auto &target_crs_code = row[idx++];
6267
0
            const auto &interpolation_crs_auth_name = row[idx++];
6268
0
            const auto &interpolation_crs_code = row[idx++];
6269
0
            const auto &operation_version = row[idx++];
6270
0
            const auto &accuracy = row[idx++];
6271
0
            const auto &deprecated_str = row[idx++];
6272
0
            const bool deprecated = deprecated_str == "1";
6273
6274
0
            const size_t base_param_idx = idx;
6275
0
            std::vector<operation::OperationParameterNNPtr> parameters;
6276
0
            std::vector<operation::ParameterValueNNPtr> values;
6277
0
            for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
6278
0
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6279
0
                if (param_auth_name.empty()) {
6280
0
                    break;
6281
0
                }
6282
0
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6283
0
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6284
0
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6285
0
                const auto &param_uom_auth_name =
6286
0
                    row[base_param_idx + i * 6 + 4];
6287
0
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6288
0
                parameters.emplace_back(operation::OperationParameter::create(
6289
0
                    util::PropertyMap()
6290
0
                        .set(metadata::Identifier::CODESPACE_KEY,
6291
0
                             param_auth_name)
6292
0
                        .set(metadata::Identifier::CODE_KEY, param_code)
6293
0
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6294
0
                std::string normalized_uom_code(param_uom_code);
6295
0
                const double normalized_value = normalizeMeasure(
6296
0
                    param_uom_code, param_value, normalized_uom_code);
6297
0
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6298
0
                                                  normalized_uom_code);
6299
0
                values.emplace_back(operation::ParameterValue::create(
6300
0
                    common::Measure(normalized_value, uom)));
6301
0
            }
6302
0
            idx = base_param_idx + 6 * N_MAX_PARAMS;
6303
0
            (void)idx;
6304
0
            assert(idx == row.size());
6305
6306
0
            auto sourceCRS =
6307
0
                d->createFactory(source_crs_auth_name)
6308
0
                    ->createCoordinateReferenceSystem(source_crs_code);
6309
0
            auto targetCRS =
6310
0
                d->createFactory(target_crs_auth_name)
6311
0
                    ->createCoordinateReferenceSystem(target_crs_code);
6312
0
            auto interpolationCRS =
6313
0
                interpolation_crs_auth_name.empty()
6314
0
                    ? nullptr
6315
0
                    : d->createFactory(interpolation_crs_auth_name)
6316
0
                          ->createCoordinateReferenceSystem(
6317
0
                              interpolation_crs_code)
6318
0
                          .as_nullable();
6319
6320
0
            auto props = d->createPropertiesSearchUsages(
6321
0
                type, code, name, deprecated, description);
6322
0
            if (!operation_version.empty()) {
6323
0
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6324
0
                          operation_version);
6325
0
            }
6326
6327
0
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6328
0
            if (!accuracy.empty()) {
6329
0
                accuracies.emplace_back(
6330
0
                    metadata::PositionalAccuracy::create(accuracy));
6331
0
            }
6332
6333
0
            if (method_auth_name == "PROJ") {
6334
0
                if (method_code == "PROJString") {
6335
0
                    auto op = operation::SingleOperation::createPROJBased(
6336
0
                        props, method_name, sourceCRS, targetCRS, accuracies);
6337
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6338
0
                    return op;
6339
0
                } else if (method_code == "WKT") {
6340
0
                    auto op = util::nn_dynamic_pointer_cast<
6341
0
                        operation::CoordinateOperation>(
6342
0
                        WKTParser().createFromWKT(method_name));
6343
0
                    if (!op) {
6344
0
                        throw FactoryException("WKT string does not express a "
6345
0
                                               "coordinate operation");
6346
0
                    }
6347
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6348
0
                    return NN_NO_CHECK(op);
6349
0
                }
6350
0
            }
6351
6352
0
            auto propsMethod =
6353
0
                util::PropertyMap()
6354
0
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6355
0
                    .set(metadata::Identifier::CODE_KEY, method_code)
6356
0
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6357
6358
0
            if (method_auth_name == metadata::Identifier::EPSG) {
6359
0
                int method_code_int = std::atoi(method_code.c_str());
6360
0
                if (operation::isAxisOrderReversal(method_code_int) ||
6361
0
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6362
0
                    method_code_int ==
6363
0
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6364
0
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6365
0
                    auto op = operation::Conversion::create(props, propsMethod,
6366
0
                                                            parameters, values);
6367
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6368
0
                    return op;
6369
0
                }
6370
0
            }
6371
0
            return operation::Transformation::create(
6372
0
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6373
0
                parameters, values, accuracies);
6374
6375
0
        } catch (const std::exception &ex) {
6376
0
            throw buildFactoryException("transformation", d->authority(), code,
6377
0
                                        ex);
6378
0
        }
6379
0
    }
6380
6381
0
    if (allowConcatenated && type == "concatenated_operation") {
6382
0
        auto res = d->runWithCodeParam(
6383
0
            "SELECT name, description, "
6384
0
            "source_crs_auth_name, source_crs_code, "
6385
0
            "target_crs_auth_name, target_crs_code, "
6386
0
            "accuracy, "
6387
0
            "operation_version, deprecated FROM "
6388
0
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6389
0
            code);
6390
0
        if (res.empty()) {
6391
            // shouldn't happen if foreign keys are OK
6392
0
            throw NoSuchAuthorityCodeException(
6393
0
                "concatenated_operation not found", d->authority(), code);
6394
0
        }
6395
6396
0
        auto resSteps = d->runWithCodeParam(
6397
0
            "SELECT step_auth_name, step_code FROM "
6398
0
            "concatenated_operation_step WHERE operation_auth_name = ? "
6399
0
            "AND operation_code = ? ORDER BY step_number",
6400
0
            code);
6401
6402
0
        try {
6403
0
            const auto &row = res.front();
6404
0
            size_t idx = 0;
6405
0
            const auto &name = row[idx++];
6406
0
            const auto &description = row[idx++];
6407
0
            const auto &source_crs_auth_name = row[idx++];
6408
0
            const auto &source_crs_code = row[idx++];
6409
0
            const auto &target_crs_auth_name = row[idx++];
6410
0
            const auto &target_crs_code = row[idx++];
6411
0
            const auto &accuracy = row[idx++];
6412
0
            const auto &operation_version = row[idx++];
6413
0
            const auto &deprecated_str = row[idx++];
6414
0
            const bool deprecated = deprecated_str == "1";
6415
6416
0
            std::vector<operation::CoordinateOperationNNPtr> operations;
6417
0
            for (const auto &rowStep : resSteps) {
6418
0
                const auto &step_auth_name = rowStep[0];
6419
0
                const auto &step_code = rowStep[1];
6420
0
                operations.push_back(
6421
0
                    d->createFactory(step_auth_name)
6422
0
                        ->createCoordinateOperation(step_code, false,
6423
0
                                                    usePROJAlternativeGridNames,
6424
0
                                                    std::string()));
6425
0
            }
6426
6427
0
            operation::ConcatenatedOperation::fixStepsDirection(
6428
0
                d->createFactory(source_crs_auth_name)
6429
0
                    ->createCoordinateReferenceSystem(source_crs_code),
6430
0
                d->createFactory(target_crs_auth_name)
6431
0
                    ->createCoordinateReferenceSystem(target_crs_code),
6432
0
                operations, d->context());
6433
6434
0
            auto props = d->createPropertiesSearchUsages(
6435
0
                type, code, name, deprecated, description);
6436
0
            if (!operation_version.empty()) {
6437
0
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6438
0
                          operation_version);
6439
0
            }
6440
6441
0
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6442
0
            if (!accuracy.empty()) {
6443
0
                accuracies.emplace_back(
6444
0
                    metadata::PositionalAccuracy::create(accuracy));
6445
0
            } else {
6446
                // Try to compute a reasonable accuracy from the members
6447
0
                double totalAcc = -1;
6448
0
                try {
6449
0
                    for (const auto &op : operations) {
6450
0
                        auto accs = op->coordinateOperationAccuracies();
6451
0
                        if (accs.size() == 1) {
6452
0
                            double acc = c_locale_stod(accs[0]->value());
6453
0
                            if (totalAcc < 0) {
6454
0
                                totalAcc = acc;
6455
0
                            } else {
6456
0
                                totalAcc += acc;
6457
0
                            }
6458
0
                        } else if (dynamic_cast<const operation::Conversion *>(
6459
0
                                       op.get())) {
6460
                            // A conversion is perfectly accurate.
6461
0
                            if (totalAcc < 0) {
6462
0
                                totalAcc = 0;
6463
0
                            }
6464
0
                        } else {
6465
0
                            totalAcc = -1;
6466
0
                            break;
6467
0
                        }
6468
0
                    }
6469
0
                    if (totalAcc >= 0) {
6470
0
                        accuracies.emplace_back(
6471
0
                            metadata::PositionalAccuracy::create(
6472
0
                                toString(totalAcc)));
6473
0
                    }
6474
0
                } catch (const std::exception &) {
6475
0
                }
6476
0
            }
6477
0
            return operation::ConcatenatedOperation::create(props, operations,
6478
0
                                                            accuracies);
6479
6480
0
        } catch (const std::exception &ex) {
6481
0
            throw buildFactoryException("transformation", d->authority(), code,
6482
0
                                        ex);
6483
0
        }
6484
0
    }
6485
6486
0
    throw FactoryException("unhandled coordinate operation type: " + type);
6487
0
}
6488
6489
// ---------------------------------------------------------------------------
6490
6491
/** \brief Returns a list operation::CoordinateOperation between two CRS.
6492
 *
6493
 * The list is ordered with preferred operations first. No attempt is made
6494
 * at inferring operations that are not explicitly in the database.
6495
 *
6496
 * Deprecated operations are rejected.
6497
 *
6498
 * @param sourceCRSCode Source CRS code allocated by authority.
6499
 * @param targetCRSCode Source CRS code allocated by authority.
6500
 * @return list of coordinate operations
6501
 * @throw NoSuchAuthorityCodeException
6502
 * @throw FactoryException
6503
 */
6504
6505
std::vector<operation::CoordinateOperationNNPtr>
6506
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
6507
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
6508
0
    return createFromCoordinateReferenceSystemCodes(
6509
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
6510
0
        false, false, false);
6511
0
}
6512
6513
// ---------------------------------------------------------------------------
6514
6515
/** \brief Returns a list of geoid models available for that crs
6516
 *
6517
 * The list includes the geoid models connected directly with the crs,
6518
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
6519
 *
6520
 * @param code crs code allocated by authority.
6521
 * @return list of geoid model names
6522
 * @throw FactoryException
6523
 */
6524
6525
std::list<std::string>
6526
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
6527
6528
0
    ListOfParams params;
6529
0
    std::string sql;
6530
0
    sql += "SELECT DISTINCT GM0.name "
6531
0
           " FROM geoid_model GM0 "
6532
0
           "INNER JOIN grid_transformation GT0 "
6533
0
           " ON  GT0.code = GM0.operation_code "
6534
0
           " AND GT0.auth_name = GM0.operation_auth_name "
6535
0
           " AND GT0.target_crs_code = ? ";
6536
0
    params.emplace_back(code);
6537
0
    if (d->hasAuthorityRestriction()) {
6538
0
        sql += " AND GT0.target_crs_auth_name = ? ";
6539
0
        params.emplace_back(d->authority());
6540
0
    }
6541
6542
    /// The second part of the query is for CRSs that use that geoid model via
6543
    /// Height Depth Reversal (EPSG:1068) or Change of Vertical Unit (EPSG:1069)
6544
0
    sql += "UNION "
6545
0
           "SELECT DISTINCT GM0.name "
6546
0
           " FROM geoid_model GM0 "
6547
0
           "INNER JOIN grid_transformation GT1 "
6548
0
           " ON  GT1.code = GM0.operation_code "
6549
0
           " AND GT1.auth_name = GM0.operation_auth_name "
6550
0
           "INNER JOIN other_transformation OT1 "
6551
0
           " ON  OT1.source_crs_code = GT1.target_crs_code "
6552
0
           " AND OT1.source_crs_auth_name = GT1.target_crs_auth_name "
6553
0
           " AND OT1.method_auth_name = 'EPSG' "
6554
0
           " AND OT1.method_code IN (1068, 1069, 1104) "
6555
0
           " AND OT1.target_crs_code = ? ";
6556
0
    params.emplace_back(code);
6557
0
    if (d->hasAuthorityRestriction()) {
6558
0
        sql += " AND OT1.target_crs_auth_name = ? ";
6559
0
        params.emplace_back(d->authority());
6560
0
    }
6561
6562
    /// The third part of the query is for CRSs that use that geoid model via
6563
    /// other_transformation table twice, like transforming depth and feet
6564
0
    sql += "UNION "
6565
0
           "SELECT DISTINCT GM0.name "
6566
0
           " FROM geoid_model GM0 "
6567
0
           "INNER JOIN grid_transformation GT1 "
6568
0
           " ON  GT1.code = GM0.operation_code "
6569
0
           " AND GT1.auth_name = GM0.operation_auth_name "
6570
0
           "INNER JOIN other_transformation OT1 "
6571
0
           " ON  OT1.source_crs_code = GT1.target_crs_code "
6572
0
           " AND OT1.source_crs_auth_name = GT1.target_crs_auth_name "
6573
0
           " AND OT1.method_auth_name = 'EPSG' "
6574
0
           " AND OT1.method_code IN (1068, 1069, 1104) "
6575
0
           "INNER JOIN other_transformation OT2 "
6576
0
           " ON  OT2.source_crs_code = OT1.target_crs_code "
6577
0
           " AND OT2.source_crs_auth_name = OT1.target_crs_auth_name "
6578
0
           " AND OT2.method_code IN (1068, 1069, 1104) "
6579
0
           " AND OT2.target_crs_code = ? ";
6580
0
    params.emplace_back(code);
6581
0
    if (d->hasAuthorityRestriction()) {
6582
0
        sql += " AND OT2.target_crs_auth_name = ? ";
6583
0
        params.emplace_back(d->authority());
6584
0
    }
6585
0
    sql += " ORDER BY 1 ";
6586
6587
0
    auto sqlRes = d->run(sql, params);
6588
0
    std::list<std::string> res;
6589
0
    for (const auto &row : sqlRes) {
6590
0
        res.push_back(row[0]);
6591
0
    }
6592
0
    return res;
6593
0
}
6594
6595
// ---------------------------------------------------------------------------
6596
6597
/** \brief Returns a list operation::CoordinateOperation between two CRS.
6598
 *
6599
 * The list is ordered with preferred operations first. No attempt is made
6600
 * at inferring operations that are not explicitly in the database (see
6601
 * createFromCRSCodesWithIntermediates() for that), and only
6602
 * source -> target operations are searched (i.e. if target -> source is
6603
 * present, you need to call this method with the arguments reversed, and apply
6604
 * the reverse transformations).
6605
 *
6606
 * Deprecated operations are rejected.
6607
 *
6608
 * If getAuthority() returns empty, then coordinate operations from all
6609
 * authorities are considered.
6610
 *
6611
 * @param sourceCRSAuthName Authority name of sourceCRSCode
6612
 * @param sourceCRSCode Source CRS code allocated by authority
6613
 * sourceCRSAuthName.
6614
 * @param targetCRSAuthName Authority name of targetCRSCode
6615
 * @param targetCRSCode Source CRS code allocated by authority
6616
 * targetCRSAuthName.
6617
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6618
 * should be substituted to the official grid names.
6619
 * @param discardIfMissingGrid Whether coordinate operations that reference
6620
 * missing grids should be removed from the result set.
6621
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
6622
 * as available (typically when network is enabled).
6623
 * @param discardSuperseded Whether coordinate operations that are superseded
6624
 * (but not deprecated) should be removed from the result set.
6625
 * @param tryReverseOrder whether to search in the reverse order too (and thus
6626
 * inverse results found that way)
6627
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
6628
 * intersectingExtent2 should be honored in a strict way.
6629
 * @param intersectingExtent1 Optional extent that the resulting operations
6630
 * must intersect.
6631
 * @param intersectingExtent2 Optional extent that the resulting operations
6632
 * must intersect.
6633
 * @return list of coordinate operations
6634
 * @throw NoSuchAuthorityCodeException
6635
 * @throw FactoryException
6636
 */
6637
6638
std::vector<operation::CoordinateOperationNNPtr>
6639
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
6640
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
6641
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
6642
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
6643
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
6644
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
6645
    const metadata::ExtentPtr &intersectingExtent1,
6646
0
    const metadata::ExtentPtr &intersectingExtent2) const {
6647
6648
0
    auto cacheKey(d->authority());
6649
0
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
6650
0
    cacheKey += sourceCRSCode;
6651
0
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
6652
0
    cacheKey += targetCRSCode;
6653
0
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
6654
0
    cacheKey += (discardIfMissingGrid ? '1' : '0');
6655
0
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
6656
0
    cacheKey += (discardSuperseded ? '1' : '0');
6657
0
    cacheKey += (tryReverseOrder ? '1' : '0');
6658
0
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
6659
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
6660
0
        if (extent) {
6661
0
            const auto &geogExtent = extent->geographicElements();
6662
0
            if (geogExtent.size() == 1) {
6663
0
                auto bbox =
6664
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
6665
0
                        geogExtent[0].get());
6666
0
                if (bbox) {
6667
0
                    cacheKey += toString(bbox->southBoundLatitude());
6668
0
                    cacheKey += toString(bbox->westBoundLongitude());
6669
0
                    cacheKey += toString(bbox->northBoundLatitude());
6670
0
                    cacheKey += toString(bbox->eastBoundLongitude());
6671
0
                }
6672
0
            }
6673
0
        }
6674
0
    }
6675
6676
0
    std::vector<operation::CoordinateOperationNNPtr> list;
6677
6678
0
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
6679
0
        return list;
6680
0
    }
6681
6682
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
6683
    // In which case use the conversion of the ProjectedCRS
6684
0
    if (!targetCRSAuthName.empty()) {
6685
0
        auto targetFactory = d->createFactory(targetCRSAuthName);
6686
0
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
6687
0
                                        targetCRSCode);
6688
0
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
6689
0
            cacheKeyProjectedCRS);
6690
0
        crs::ProjectedCRSPtr targetProjCRS;
6691
0
        if (crs) {
6692
0
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
6693
0
        } else {
6694
0
            const auto sqlRes =
6695
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
6696
0
            if (!sqlRes.empty()) {
6697
0
                try {
6698
0
                    targetProjCRS =
6699
0
                        targetFactory->d
6700
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
6701
0
                            .as_nullable();
6702
0
                } catch (const std::exception &) {
6703
0
                }
6704
0
            }
6705
0
        }
6706
0
        if (targetProjCRS) {
6707
0
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
6708
0
            if (sourceCRSAuthName.empty() ||
6709
0
                (!baseIds.empty() &&
6710
0
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
6711
0
                 baseIds.front()->code() == sourceCRSCode)) {
6712
0
                bool ok = true;
6713
0
                auto conv = targetProjCRS->derivingConversion();
6714
0
                if (d->hasAuthorityRestriction()) {
6715
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
6716
0
                         d->authority();
6717
0
                }
6718
0
                if (ok) {
6719
0
                    list.emplace_back(conv);
6720
0
                    d->context()->d->cache(cacheKey, list);
6721
0
                    return list;
6722
0
                }
6723
0
            }
6724
0
        }
6725
0
    }
6726
6727
0
    std::string sql;
6728
0
    if (discardSuperseded) {
6729
0
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
6730
0
              "cov.target_crs_auth_name, cov.target_crs_code, "
6731
0
              "cov.auth_name, cov.code, cov.table_name, "
6732
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
6733
0
              "extent.east_lon, "
6734
0
              "ss.replacement_auth_name, ss.replacement_code, "
6735
0
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
6736
0
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
6737
0
              "FROM "
6738
0
              "coordinate_operation_view cov "
6739
0
              "JOIN usage ON "
6740
0
              "usage.object_table_name = cov.table_name AND "
6741
0
              "usage.object_auth_name = cov.auth_name AND "
6742
0
              "usage.object_code = cov.code "
6743
0
              "JOIN extent "
6744
0
              "ON extent.auth_name = usage.extent_auth_name AND "
6745
0
              "extent.code = usage.extent_code "
6746
0
              "LEFT JOIN supersession ss ON "
6747
0
              "ss.superseded_table_name = cov.table_name AND "
6748
0
              "ss.superseded_auth_name = cov.auth_name AND "
6749
0
              "ss.superseded_code = cov.code AND "
6750
0
              "ss.superseded_table_name = ss.replacement_table_name AND "
6751
0
              "ss.same_source_target_crs = 1 "
6752
0
              "LEFT JOIN grid_transformation gt ON "
6753
0
              "gt.auth_name = ss.replacement_auth_name AND "
6754
0
              "gt.code = ss.replacement_code "
6755
0
              "LEFT JOIN grid_alternatives ga ON "
6756
0
              "ga.original_grid_name = gt.grid_name "
6757
0
              "WHERE ";
6758
0
    } else {
6759
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
6760
0
              "target_crs_auth_name, target_crs_code, "
6761
0
              "cov.auth_name, cov.code, cov.table_name, "
6762
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
6763
0
              "extent.east_lon "
6764
0
              "FROM "
6765
0
              "coordinate_operation_view cov "
6766
0
              "JOIN usage ON "
6767
0
              "usage.object_table_name = cov.table_name AND "
6768
0
              "usage.object_auth_name = cov.auth_name AND "
6769
0
              "usage.object_code = cov.code "
6770
0
              "JOIN extent "
6771
0
              "ON extent.auth_name = usage.extent_auth_name AND "
6772
0
              "extent.code = usage.extent_code "
6773
0
              "WHERE ";
6774
0
    }
6775
0
    ListOfParams params;
6776
0
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
6777
0
        if (tryReverseOrder) {
6778
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
6779
0
                   "AND "
6780
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
6781
0
                   "OR "
6782
0
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
6783
0
                   "AND "
6784
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
6785
0
                   "AND ";
6786
0
            params.emplace_back(sourceCRSAuthName);
6787
0
            params.emplace_back(sourceCRSCode);
6788
0
            params.emplace_back(targetCRSAuthName);
6789
0
            params.emplace_back(targetCRSCode);
6790
0
            params.emplace_back(targetCRSAuthName);
6791
0
            params.emplace_back(targetCRSCode);
6792
0
            params.emplace_back(sourceCRSAuthName);
6793
0
            params.emplace_back(sourceCRSCode);
6794
0
        } else {
6795
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
6796
0
                   "AND "
6797
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
6798
0
                   "AND ";
6799
0
            params.emplace_back(sourceCRSAuthName);
6800
0
            params.emplace_back(sourceCRSCode);
6801
0
            params.emplace_back(targetCRSAuthName);
6802
0
            params.emplace_back(targetCRSCode);
6803
0
        }
6804
0
    } else if (!sourceCRSAuthName.empty()) {
6805
0
        if (tryReverseOrder) {
6806
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
6807
0
                   ")OR "
6808
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
6809
0
                   " AND ";
6810
0
            params.emplace_back(sourceCRSAuthName);
6811
0
            params.emplace_back(sourceCRSCode);
6812
0
            params.emplace_back(sourceCRSAuthName);
6813
0
            params.emplace_back(sourceCRSCode);
6814
0
        } else {
6815
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
6816
0
                   "AND ";
6817
0
            params.emplace_back(sourceCRSAuthName);
6818
0
            params.emplace_back(sourceCRSCode);
6819
0
        }
6820
0
    } else if (!targetCRSAuthName.empty()) {
6821
0
        if (tryReverseOrder) {
6822
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
6823
0
                   " OR "
6824
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
6825
0
                   " AND ";
6826
0
            params.emplace_back(targetCRSAuthName);
6827
0
            params.emplace_back(targetCRSCode);
6828
0
            params.emplace_back(targetCRSAuthName);
6829
0
            params.emplace_back(targetCRSCode);
6830
0
        } else {
6831
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
6832
0
                   "AND ";
6833
0
            params.emplace_back(targetCRSAuthName);
6834
0
            params.emplace_back(targetCRSCode);
6835
0
        }
6836
0
    }
6837
0
    sql += "cov.deprecated = 0";
6838
0
    if (d->hasAuthorityRestriction()) {
6839
0
        sql += " AND cov.auth_name = ?";
6840
0
        params.emplace_back(d->authority());
6841
0
    }
6842
0
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
6843
0
           "east_lon) DESC, "
6844
0
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
6845
0
    auto res = d->run(sql, params);
6846
0
    std::set<std::pair<std::string, std::string>> setTransf;
6847
0
    if (discardSuperseded) {
6848
0
        for (const auto &row : res) {
6849
0
            const auto &auth_name = row[4];
6850
0
            const auto &code = row[5];
6851
0
            setTransf.insert(
6852
0
                std::pair<std::string, std::string>(auth_name, code));
6853
0
        }
6854
0
    }
6855
6856
    // Do a pass to determine if there are transformations that intersect
6857
    // intersectingExtent1 & intersectingExtent2
6858
0
    std::vector<bool> intersectingTransformations;
6859
0
    intersectingTransformations.resize(res.size());
6860
0
    bool hasIntersectingTransformations = false;
6861
0
    size_t i = 0;
6862
0
    for (const auto &row : res) {
6863
0
        size_t thisI = i;
6864
0
        ++i;
6865
0
        if (discardSuperseded) {
6866
0
            const auto &replacement_auth_name = row[11];
6867
0
            const auto &replacement_code = row[12];
6868
0
            const bool replacement_is_grid_transform = row[13] == "1";
6869
0
            const bool replacement_is_known_grid = row[14] == "1";
6870
0
            if (!replacement_auth_name.empty() &&
6871
                // Ignore supersession if the replacement uses a unknown grid
6872
0
                !(replacement_is_grid_transform &&
6873
0
                  !replacement_is_known_grid) &&
6874
0
                setTransf.find(std::pair<std::string, std::string>(
6875
0
                    replacement_auth_name, replacement_code)) !=
6876
0
                    setTransf.end()) {
6877
                // Skip transformations that are superseded by others that got
6878
                // returned in the result set.
6879
0
                continue;
6880
0
            }
6881
0
        }
6882
6883
0
        bool intersecting = true;
6884
0
        try {
6885
0
            double south_lat = c_locale_stod(row[7]);
6886
0
            double west_lon = c_locale_stod(row[8]);
6887
0
            double north_lat = c_locale_stod(row[9]);
6888
0
            double east_lon = c_locale_stod(row[10]);
6889
0
            auto transf_extent = metadata::Extent::createFromBBOX(
6890
0
                west_lon, south_lat, east_lon, north_lat);
6891
6892
0
            for (const auto &extent :
6893
0
                 {intersectingExtent1, intersectingExtent2}) {
6894
0
                if (extent) {
6895
0
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
6896
0
                        intersecting = false;
6897
0
                        break;
6898
0
                    }
6899
0
                }
6900
0
            }
6901
0
        } catch (const std::exception &) {
6902
0
        }
6903
6904
0
        intersectingTransformations[thisI] = intersecting;
6905
0
        if (intersecting)
6906
0
            hasIntersectingTransformations = true;
6907
0
    }
6908
6909
    // If there are intersecting transformations, then only report those ones
6910
    // If there are no intersecting transformations, report all of them
6911
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
6912
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
6913
    // coordinates are used
6914
0
    i = 0;
6915
0
    for (const auto &row : res) {
6916
0
        size_t thisI = i;
6917
0
        ++i;
6918
0
        if ((hasIntersectingTransformations ||
6919
0
             reportOnlyIntersectingTransformations) &&
6920
0
            !intersectingTransformations[thisI]) {
6921
0
            continue;
6922
0
        }
6923
0
        if (discardSuperseded) {
6924
0
            const auto &replacement_auth_name = row[11];
6925
0
            const auto &replacement_code = row[12];
6926
0
            const bool replacement_is_grid_transform = row[13] == "1";
6927
0
            const bool replacement_is_known_grid = row[14] == "1";
6928
0
            if (!replacement_auth_name.empty() &&
6929
                // Ignore supersession if the replacement uses a unknown grid
6930
0
                !(replacement_is_grid_transform &&
6931
0
                  !replacement_is_known_grid) &&
6932
0
                setTransf.find(std::pair<std::string, std::string>(
6933
0
                    replacement_auth_name, replacement_code)) !=
6934
0
                    setTransf.end()) {
6935
                // Skip transformations that are superseded by others that got
6936
                // returned in the result set.
6937
0
                continue;
6938
0
            }
6939
0
        }
6940
6941
0
        const auto &source_crs_auth_name = row[0];
6942
0
        const auto &source_crs_code = row[1];
6943
0
        const auto &target_crs_auth_name = row[2];
6944
0
        const auto &target_crs_code = row[3];
6945
0
        const auto &auth_name = row[4];
6946
0
        const auto &code = row[5];
6947
0
        const auto &table_name = row[6];
6948
0
        try {
6949
0
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
6950
0
                code, true, usePROJAlternativeGridNames, table_name);
6951
0
            if (tryReverseOrder &&
6952
0
                (!sourceCRSAuthName.empty()
6953
0
                     ? (source_crs_auth_name != sourceCRSAuthName ||
6954
0
                        source_crs_code != sourceCRSCode)
6955
0
                     : (target_crs_auth_name != targetCRSAuthName ||
6956
0
                        target_crs_code != targetCRSCode))) {
6957
0
                op = op->inverse();
6958
0
            }
6959
0
            if (!discardIfMissingGrid ||
6960
0
                !d->rejectOpDueToMissingGrid(op,
6961
0
                                             considerKnownGridsAsAvailable)) {
6962
0
                list.emplace_back(op);
6963
0
            }
6964
0
        } catch (const std::exception &e) {
6965
            // Mostly for debugging purposes when using an inconsistent
6966
            // database
6967
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
6968
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
6969
0
            } else {
6970
0
                throw;
6971
0
            }
6972
0
        }
6973
0
    }
6974
0
    d->context()->d->cache(cacheKey, list);
6975
0
    return list;
6976
0
}
6977
6978
// ---------------------------------------------------------------------------
6979
6980
//! @cond Doxygen_Suppress
6981
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
6982
                               const std::string &sourceCRSAuthName,
6983
                               const std::string &sourceCRSCode,
6984
                               const std::string &targetCRSAuthName,
6985
0
                               const std::string &targetCRSCode) {
6986
0
    auto concat =
6987
0
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
6988
0
    if (!concat) {
6989
0
        return false;
6990
0
    }
6991
0
    auto ops = concat->operations();
6992
0
    for (size_t i = 0; i + 1 < ops.size(); i++) {
6993
0
        auto targetCRS = ops[i]->targetCRS();
6994
0
        if (targetCRS) {
6995
0
            const auto &ids = targetCRS->identifiers();
6996
0
            if (ids.size() == 1 &&
6997
0
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
6998
0
                  ids[0]->code() == sourceCRSCode) ||
6999
0
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7000
0
                  ids[0]->code() == targetCRSCode))) {
7001
0
                return true;
7002
0
            }
7003
0
        }
7004
0
    }
7005
0
    return false;
7006
0
}
7007
//! @endcond
7008
7009
// ---------------------------------------------------------------------------
7010
7011
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7012
 * using intermediate codes.
7013
 *
7014
 * The list is ordered with preferred operations first.
7015
 *
7016
 * Deprecated operations are rejected.
7017
 *
7018
 * The method will take care of considering all potential combinations (i.e.
7019
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7020
 * call it with sourceCRS and targetCRS switched)
7021
 *
7022
 * If getAuthority() returns empty, then coordinate operations from all
7023
 * authorities are considered.
7024
 *
7025
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7026
 * @param sourceCRSCode Source CRS code allocated by authority
7027
 * sourceCRSAuthName.
7028
 * @param targetCRSAuthName Authority name of targetCRSCode
7029
 * @param targetCRSCode Source CRS code allocated by authority
7030
 * targetCRSAuthName.
7031
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7032
 * should be substituted to the official grid names.
7033
 * @param discardIfMissingGrid Whether coordinate operations that reference
7034
 * missing grids should be removed from the result set.
7035
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7036
 * as available (typically when network is enabled).
7037
 * @param discardSuperseded Whether coordinate operations that are superseded
7038
 * (but not deprecated) should be removed from the result set.
7039
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7040
 * used as potential intermediate CRS. If the list is empty, the database will
7041
 * be used to find common CRS in operations involving both the source and
7042
 * target CRS.
7043
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7044
 * object considered.
7045
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7046
 * @param allowedAuthorities One or several authority name allowed for the two
7047
 * coordinate operations that are going to be searched. When this vector is
7048
 * no empty, it overrides the authority of this object. This is useful for
7049
 * example when the coordinate operations to chain belong to two different
7050
 * allowed authorities.
7051
 * @param intersectingExtent1 Optional extent that the resulting operations
7052
 * must intersect.
7053
 * @param intersectingExtent2 Optional extent that the resulting operations
7054
 * must intersect.
7055
 * @return list of coordinate operations
7056
 * @throw NoSuchAuthorityCodeException
7057
 * @throw FactoryException
7058
 */
7059
7060
std::vector<operation::CoordinateOperationNNPtr>
7061
AuthorityFactory::createFromCRSCodesWithIntermediates(
7062
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7063
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7064
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7065
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7066
    const std::vector<std::pair<std::string, std::string>>
7067
        &intermediateCRSAuthCodes,
7068
    ObjectType allowedIntermediateObjectType,
7069
    const std::vector<std::string> &allowedAuthorities,
7070
    const metadata::ExtentPtr &intersectingExtent1,
7071
0
    const metadata::ExtentPtr &intersectingExtent2) const {
7072
7073
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7074
7075
0
    if (sourceCRSAuthName == targetCRSAuthName &&
7076
0
        sourceCRSCode == targetCRSCode) {
7077
0
        return listTmp;
7078
0
    }
7079
7080
0
    const auto CheckIfHasOperations = [=](const std::string &auth_name,
7081
0
                                          const std::string &code) {
7082
0
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7083
0
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7084
0
                        "(target_crs_auth_name = ? AND target_crs_code = ?)",
7085
0
                        {auth_name, code, auth_name, code})
7086
0
                     .empty());
7087
0
    };
7088
7089
    // If the source or target CRS are not the source or target of an operation,
7090
    // do not run the next costly requests.
7091
0
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7092
0
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7093
0
        return listTmp;
7094
0
    }
7095
7096
0
    const std::string sqlProlog(
7097
0
        discardSuperseded
7098
0
            ?
7099
7100
0
            "SELECT v1.table_name as table1, "
7101
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7102
0
            "v1.accuracy AS accuracy1, "
7103
0
            "v2.table_name as table2, "
7104
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7105
0
            "v2.accuracy as accuracy2, "
7106
0
            "a1.south_lat AS south_lat1, "
7107
0
            "a1.west_lon AS west_lon1, "
7108
0
            "a1.north_lat AS north_lat1, "
7109
0
            "a1.east_lon AS east_lon1, "
7110
0
            "a2.south_lat AS south_lat2, "
7111
0
            "a2.west_lon AS west_lon2, "
7112
0
            "a2.north_lat AS north_lat2, "
7113
0
            "a2.east_lon AS east_lon2, "
7114
0
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7115
0
            "ss1.replacement_code AS replacement_code1, "
7116
0
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7117
0
            "ss2.replacement_code AS replacement_code2 "
7118
0
            "FROM coordinate_operation_view v1 "
7119
0
            "JOIN coordinate_operation_view v2 "
7120
0
            :
7121
7122
0
            "SELECT v1.table_name as table1, "
7123
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7124
0
            "v1.accuracy AS accuracy1, "
7125
0
            "v2.table_name as table2, "
7126
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7127
0
            "v2.accuracy as accuracy2, "
7128
0
            "a1.south_lat AS south_lat1, "
7129
0
            "a1.west_lon AS west_lon1, "
7130
0
            "a1.north_lat AS north_lat1, "
7131
0
            "a1.east_lon AS east_lon1, "
7132
0
            "a2.south_lat AS south_lat2, "
7133
0
            "a2.west_lon AS west_lon2, "
7134
0
            "a2.north_lat AS north_lat2, "
7135
0
            "a2.east_lon AS east_lon2 "
7136
0
            "FROM coordinate_operation_view v1 "
7137
0
            "JOIN coordinate_operation_view v2 ");
7138
7139
0
    const std::string joinSupersession(
7140
0
        "LEFT JOIN supersession ss1 ON "
7141
0
        "ss1.superseded_table_name = v1.table_name AND "
7142
0
        "ss1.superseded_auth_name = v1.auth_name AND "
7143
0
        "ss1.superseded_code = v1.code AND "
7144
0
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7145
0
        "ss1.same_source_target_crs = 1 "
7146
0
        "LEFT JOIN supersession ss2 ON "
7147
0
        "ss2.superseded_table_name = v2.table_name AND "
7148
0
        "ss2.superseded_auth_name = v2.auth_name AND "
7149
0
        "ss2.superseded_code = v2.code AND "
7150
0
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7151
0
        "ss2.same_source_target_crs = 1 ");
7152
0
    const std::string joinArea(
7153
0
        (discardSuperseded ? joinSupersession : std::string()) +
7154
0
        "JOIN usage u1 ON "
7155
0
        "u1.object_table_name = v1.table_name AND "
7156
0
        "u1.object_auth_name = v1.auth_name AND "
7157
0
        "u1.object_code = v1.code "
7158
0
        "JOIN extent a1 "
7159
0
        "ON a1.auth_name = u1.extent_auth_name AND "
7160
0
        "a1.code = u1.extent_code "
7161
0
        "JOIN usage u2 ON "
7162
0
        "u2.object_table_name = v2.table_name AND "
7163
0
        "u2.object_auth_name = v2.auth_name AND "
7164
0
        "u2.object_code = v2.code "
7165
0
        "JOIN extent a2 "
7166
0
        "ON a2.auth_name = u2.extent_auth_name AND "
7167
0
        "a2.code = u2.extent_code ");
7168
0
    const std::string orderBy(
7169
0
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7170
0
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7171
0
        "accuracy1 + accuracy2");
7172
7173
    // Case (source->intermediate) and (intermediate->target)
7174
0
    std::string sql(
7175
0
        sqlProlog +
7176
0
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7177
0
        "AND v1.target_crs_code = v2.source_crs_code " +
7178
0
        joinArea +
7179
0
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7180
0
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7181
0
    std::string minDate;
7182
0
    std::string criterionOnIntermediateCRS;
7183
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7184
0
        auto sourceCRS = d->createFactory(sourceCRSAuthName)
7185
0
                             ->createGeodeticCRS(sourceCRSCode);
7186
0
        auto targetCRS = d->createFactory(targetCRSAuthName)
7187
0
                             ->createGeodeticCRS(targetCRSCode);
7188
0
        const auto &sourceDatum = sourceCRS->datum();
7189
0
        const auto &targetDatum = targetCRS->datum();
7190
0
        if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7191
0
            targetDatum && targetDatum->publicationDate().has_value()) {
7192
0
            const auto sourceDate(sourceDatum->publicationDate()->toString());
7193
0
            const auto targetDate(targetDatum->publicationDate()->toString());
7194
0
            minDate = std::min(sourceDate, targetDate);
7195
            // Check that the datum of the intermediateCRS has a publication
7196
            // date most recent that the one of the source and the target CRS
7197
            // Except when using the usual WGS84 pivot which happens to have a
7198
            // NULL publication date.
7199
0
            criterionOnIntermediateCRS =
7200
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7201
0
                "JOIN geodetic_datum y "
7202
0
                "ON "
7203
0
                "y.auth_name = x.datum_auth_name AND "
7204
0
                "y.code = x.datum_code "
7205
0
                "WHERE "
7206
0
                "x.auth_name = v1.target_crs_auth_name AND "
7207
0
                "x.code = v1.target_crs_code AND "
7208
0
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
7209
0
                "(y.publication_date IS NULL OR "
7210
0
                "(y.publication_date >= '" +
7211
0
                minDate + "'))) ";
7212
0
        } else {
7213
0
            criterionOnIntermediateCRS =
7214
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7215
0
                "x.auth_name = v1.target_crs_auth_name AND "
7216
0
                "x.code = v1.target_crs_code AND "
7217
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7218
0
        }
7219
0
        sql += criterionOnIntermediateCRS;
7220
0
    }
7221
0
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7222
0
                               targetCRSAuthName, targetCRSCode};
7223
0
    std::string additionalWhere(
7224
0
        "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7225
0
        "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, "
7226
0
        "south_lat2, west_lon2, north_lat2, east_lon2) = 1 ");
7227
0
    if (!allowedAuthorities.empty()) {
7228
0
        additionalWhere += "AND v1.auth_name IN (";
7229
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7230
0
            if (i > 0)
7231
0
                additionalWhere += ',';
7232
0
            additionalWhere += '?';
7233
0
        }
7234
0
        additionalWhere += ") AND v2.auth_name IN (";
7235
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7236
0
            if (i > 0)
7237
0
                additionalWhere += ',';
7238
0
            additionalWhere += '?';
7239
0
        }
7240
0
        additionalWhere += ')';
7241
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7242
0
            params.emplace_back(allowedAuthority);
7243
0
        }
7244
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7245
0
            params.emplace_back(allowedAuthority);
7246
0
        }
7247
0
    }
7248
0
    if (d->hasAuthorityRestriction()) {
7249
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7250
0
        params.emplace_back(d->authority());
7251
0
        params.emplace_back(d->authority());
7252
0
    }
7253
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7254
0
        if (extent) {
7255
0
            const auto &geogExtent = extent->geographicElements();
7256
0
            if (geogExtent.size() == 1) {
7257
0
                auto bbox =
7258
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7259
0
                        geogExtent[0].get());
7260
0
                if (bbox) {
7261
0
                    const double south_lat = bbox->southBoundLatitude();
7262
0
                    const double west_lon = bbox->westBoundLongitude();
7263
0
                    const double north_lat = bbox->northBoundLatitude();
7264
0
                    const double east_lon = bbox->eastBoundLongitude();
7265
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7266
0
                        north_lat != 90.0 || east_lon != 180.0) {
7267
0
                        additionalWhere +=
7268
0
                            "AND intersects_bbox(south_lat1, "
7269
0
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7270
0
                            "intersects_bbox(south_lat2, west_lon2, "
7271
0
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7272
0
                        params.emplace_back(south_lat);
7273
0
                        params.emplace_back(west_lon);
7274
0
                        params.emplace_back(north_lat);
7275
0
                        params.emplace_back(east_lon);
7276
0
                        params.emplace_back(south_lat);
7277
0
                        params.emplace_back(west_lon);
7278
0
                        params.emplace_back(north_lat);
7279
0
                        params.emplace_back(east_lon);
7280
0
                    }
7281
0
                }
7282
0
            }
7283
0
        }
7284
0
    }
7285
7286
0
    const auto buildIntermediateWhere =
7287
0
        [&intermediateCRSAuthCodes](const std::string &first_field,
7288
0
                                    const std::string &second_field) {
7289
0
            if (intermediateCRSAuthCodes.empty()) {
7290
0
                return std::string();
7291
0
            }
7292
0
            std::string l_sql(" AND (");
7293
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7294
0
                if (i > 0) {
7295
0
                    l_sql += " OR";
7296
0
                }
7297
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7298
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7299
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7300
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7301
0
            }
7302
0
            l_sql += ')';
7303
0
            return l_sql;
7304
0
        };
7305
7306
0
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7307
0
    for (const auto &pair : intermediateCRSAuthCodes) {
7308
0
        params.emplace_back(pair.first);
7309
0
        params.emplace_back(pair.second);
7310
0
        params.emplace_back(pair.first);
7311
0
        params.emplace_back(pair.second);
7312
0
    }
7313
0
    auto res =
7314
0
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7315
7316
0
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7317
0
        std::set<std::pair<std::string, std::string>> setTransf1;
7318
0
        std::set<std::pair<std::string, std::string>> setTransf2;
7319
0
        for (const auto &row : resultSet) {
7320
            // table1
7321
0
            const auto &auth_name1 = row[1];
7322
0
            const auto &code1 = row[2];
7323
            // accuracy1
7324
            // table2
7325
0
            const auto &auth_name2 = row[5];
7326
0
            const auto &code2 = row[6];
7327
0
            setTransf1.insert(
7328
0
                std::pair<std::string, std::string>(auth_name1, code1));
7329
0
            setTransf2.insert(
7330
0
                std::pair<std::string, std::string>(auth_name2, code2));
7331
0
        }
7332
0
        SQLResultSet filteredResultSet;
7333
0
        for (const auto &row : resultSet) {
7334
0
            const auto &replacement_auth_name1 = row[16];
7335
0
            const auto &replacement_code1 = row[17];
7336
0
            const auto &replacement_auth_name2 = row[18];
7337
0
            const auto &replacement_code2 = row[19];
7338
0
            if (!replacement_auth_name1.empty() &&
7339
0
                setTransf1.find(std::pair<std::string, std::string>(
7340
0
                    replacement_auth_name1, replacement_code1)) !=
7341
0
                    setTransf1.end()) {
7342
                // Skip transformations that are superseded by others that got
7343
                // returned in the result set.
7344
0
                continue;
7345
0
            }
7346
0
            if (!replacement_auth_name2.empty() &&
7347
0
                setTransf2.find(std::pair<std::string, std::string>(
7348
0
                    replacement_auth_name2, replacement_code2)) !=
7349
0
                    setTransf2.end()) {
7350
                // Skip transformations that are superseded by others that got
7351
                // returned in the result set.
7352
0
                continue;
7353
0
            }
7354
0
            filteredResultSet.emplace_back(row);
7355
0
        }
7356
0
        return filteredResultSet;
7357
0
    };
7358
7359
0
    if (discardSuperseded) {
7360
0
        res = filterOutSuperseded(std::move(res));
7361
0
    }
7362
0
    for (const auto &row : res) {
7363
0
        const auto &table1 = row[0];
7364
0
        const auto &auth_name1 = row[1];
7365
0
        const auto &code1 = row[2];
7366
        // const auto &accuracy1 = row[3];
7367
0
        const auto &table2 = row[4];
7368
0
        const auto &auth_name2 = row[5];
7369
0
        const auto &code2 = row[6];
7370
        // const auto &accuracy2 = row[7];
7371
0
        try {
7372
0
            auto op1 =
7373
0
                d->createFactory(auth_name1)
7374
0
                    ->createCoordinateOperation(
7375
0
                        code1, true, usePROJAlternativeGridNames, table1);
7376
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7377
0
                                   targetCRSAuthName, targetCRSCode)) {
7378
0
                continue;
7379
0
            }
7380
0
            auto op2 =
7381
0
                d->createFactory(auth_name2)
7382
0
                    ->createCoordinateOperation(
7383
0
                        code2, true, usePROJAlternativeGridNames, table2);
7384
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7385
0
                                   targetCRSAuthName, targetCRSCode)) {
7386
0
                continue;
7387
0
            }
7388
7389
0
            listTmp.emplace_back(
7390
0
                operation::ConcatenatedOperation::createComputeMetadata(
7391
0
                    {std::move(op1), std::move(op2)}, false));
7392
0
        } catch (const std::exception &e) {
7393
            // Mostly for debugging purposes when using an inconsistent
7394
            // database
7395
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7396
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7397
0
            } else {
7398
0
                throw;
7399
0
            }
7400
0
        }
7401
0
    }
7402
7403
    // Case (source->intermediate) and (target->intermediate)
7404
0
    sql = sqlProlog +
7405
0
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
7406
0
          "AND v1.target_crs_code = v2.target_crs_code " +
7407
0
          joinArea +
7408
0
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7409
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
7410
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7411
0
        sql += criterionOnIntermediateCRS;
7412
0
    }
7413
0
    intermediateWhere = buildIntermediateWhere("target", "target");
7414
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7415
0
    if (discardSuperseded) {
7416
0
        res = filterOutSuperseded(std::move(res));
7417
0
    }
7418
0
    for (const auto &row : res) {
7419
0
        const auto &table1 = row[0];
7420
0
        const auto &auth_name1 = row[1];
7421
0
        const auto &code1 = row[2];
7422
        // const auto &accuracy1 = row[3];
7423
0
        const auto &table2 = row[4];
7424
0
        const auto &auth_name2 = row[5];
7425
0
        const auto &code2 = row[6];
7426
        // const auto &accuracy2 = row[7];
7427
0
        try {
7428
0
            auto op1 =
7429
0
                d->createFactory(auth_name1)
7430
0
                    ->createCoordinateOperation(
7431
0
                        code1, true, usePROJAlternativeGridNames, table1);
7432
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7433
0
                                   targetCRSAuthName, targetCRSCode)) {
7434
0
                continue;
7435
0
            }
7436
0
            auto op2 =
7437
0
                d->createFactory(auth_name2)
7438
0
                    ->createCoordinateOperation(
7439
0
                        code2, true, usePROJAlternativeGridNames, table2);
7440
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7441
0
                                   targetCRSAuthName, targetCRSCode)) {
7442
0
                continue;
7443
0
            }
7444
7445
0
            listTmp.emplace_back(
7446
0
                operation::ConcatenatedOperation::createComputeMetadata(
7447
0
                    {std::move(op1), op2->inverse()}, false));
7448
0
        } catch (const std::exception &e) {
7449
            // Mostly for debugging purposes when using an inconsistent
7450
            // database
7451
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7452
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7453
0
            } else {
7454
0
                throw;
7455
0
            }
7456
0
        }
7457
0
    }
7458
7459
    // Case (intermediate->source) and (intermediate->target)
7460
0
    sql = sqlProlog +
7461
0
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
7462
0
          "AND v1.source_crs_code = v2.source_crs_code " +
7463
0
          joinArea +
7464
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
7465
0
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
7466
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7467
0
        if (!minDate.empty()) {
7468
0
            criterionOnIntermediateCRS =
7469
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7470
0
                "JOIN geodetic_datum y "
7471
0
                "ON "
7472
0
                "y.auth_name = x.datum_auth_name AND "
7473
0
                "y.code = x.datum_code "
7474
0
                "WHERE "
7475
0
                "x.auth_name = v1.source_crs_auth_name AND "
7476
0
                "x.code = v1.source_crs_code AND "
7477
0
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
7478
0
                "(y.publication_date IS NULL OR "
7479
0
                "(y.publication_date >= '" +
7480
0
                minDate + "'))) ";
7481
0
        } else {
7482
0
            criterionOnIntermediateCRS =
7483
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7484
0
                "x.auth_name = v1.source_crs_auth_name AND "
7485
0
                "x.code = v1.source_crs_code AND "
7486
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7487
0
        }
7488
0
        sql += criterionOnIntermediateCRS;
7489
0
    }
7490
0
    intermediateWhere = buildIntermediateWhere("source", "source");
7491
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7492
0
    if (discardSuperseded) {
7493
0
        res = filterOutSuperseded(std::move(res));
7494
0
    }
7495
0
    for (const auto &row : res) {
7496
0
        const auto &table1 = row[0];
7497
0
        const auto &auth_name1 = row[1];
7498
0
        const auto &code1 = row[2];
7499
        // const auto &accuracy1 = row[3];
7500
0
        const auto &table2 = row[4];
7501
0
        const auto &auth_name2 = row[5];
7502
0
        const auto &code2 = row[6];
7503
        // const auto &accuracy2 = row[7];
7504
0
        try {
7505
0
            auto op1 =
7506
0
                d->createFactory(auth_name1)
7507
0
                    ->createCoordinateOperation(
7508
0
                        code1, true, usePROJAlternativeGridNames, table1);
7509
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7510
0
                                   targetCRSAuthName, targetCRSCode)) {
7511
0
                continue;
7512
0
            }
7513
0
            auto op2 =
7514
0
                d->createFactory(auth_name2)
7515
0
                    ->createCoordinateOperation(
7516
0
                        code2, true, usePROJAlternativeGridNames, table2);
7517
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7518
0
                                   targetCRSAuthName, targetCRSCode)) {
7519
0
                continue;
7520
0
            }
7521
7522
0
            listTmp.emplace_back(
7523
0
                operation::ConcatenatedOperation::createComputeMetadata(
7524
0
                    {op1->inverse(), std::move(op2)}, false));
7525
0
        } catch (const std::exception &e) {
7526
            // Mostly for debugging purposes when using an inconsistent
7527
            // database
7528
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7529
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7530
0
            } else {
7531
0
                throw;
7532
0
            }
7533
0
        }
7534
0
    }
7535
7536
    // Case (intermediate->source) and (target->intermediate)
7537
0
    sql = sqlProlog +
7538
0
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
7539
0
          "AND v1.source_crs_code = v2.target_crs_code " +
7540
0
          joinArea +
7541
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
7542
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
7543
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7544
0
        sql += criterionOnIntermediateCRS;
7545
0
    }
7546
0
    intermediateWhere = buildIntermediateWhere("source", "target");
7547
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7548
0
    if (discardSuperseded) {
7549
0
        res = filterOutSuperseded(std::move(res));
7550
0
    }
7551
0
    for (const auto &row : res) {
7552
0
        const auto &table1 = row[0];
7553
0
        const auto &auth_name1 = row[1];
7554
0
        const auto &code1 = row[2];
7555
        // const auto &accuracy1 = row[3];
7556
0
        const auto &table2 = row[4];
7557
0
        const auto &auth_name2 = row[5];
7558
0
        const auto &code2 = row[6];
7559
        // const auto &accuracy2 = row[7];
7560
0
        try {
7561
0
            auto op1 =
7562
0
                d->createFactory(auth_name1)
7563
0
                    ->createCoordinateOperation(
7564
0
                        code1, true, usePROJAlternativeGridNames, table1);
7565
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7566
0
                                   targetCRSAuthName, targetCRSCode)) {
7567
0
                continue;
7568
0
            }
7569
0
            auto op2 =
7570
0
                d->createFactory(auth_name2)
7571
0
                    ->createCoordinateOperation(
7572
0
                        code2, true, usePROJAlternativeGridNames, table2);
7573
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7574
0
                                   targetCRSAuthName, targetCRSCode)) {
7575
0
                continue;
7576
0
            }
7577
7578
0
            listTmp.emplace_back(
7579
0
                operation::ConcatenatedOperation::createComputeMetadata(
7580
0
                    {op1->inverse(), op2->inverse()}, false));
7581
0
        } catch (const std::exception &e) {
7582
            // Mostly for debugging purposes when using an inconsistent
7583
            // database
7584
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7585
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7586
0
            } else {
7587
0
                throw;
7588
0
            }
7589
0
        }
7590
0
    }
7591
7592
0
    std::vector<operation::CoordinateOperationNNPtr> list;
7593
0
    for (const auto &op : listTmp) {
7594
0
        if (!discardIfMissingGrid ||
7595
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
7596
0
            list.emplace_back(op);
7597
0
        }
7598
0
    }
7599
7600
0
    return list;
7601
0
}
7602
7603
// ---------------------------------------------------------------------------
7604
7605
//! @cond Doxygen_Suppress
7606
7607
struct TrfmInfo {
7608
    std::string situation{};
7609
    std::string table_name{};
7610
    std::string auth_name{};
7611
    std::string code{};
7612
    std::string name{};
7613
    double west = 0;
7614
    double south = 0;
7615
    double east = 0;
7616
    double north = 0;
7617
};
7618
7619
// ---------------------------------------------------------------------------
7620
7621
std::vector<operation::CoordinateOperationNNPtr>
7622
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
7623
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
7624
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
7625
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7626
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7627
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7628
    const std::vector<std::string> &allowedAuthorities,
7629
    const metadata::ExtentPtr &intersectingExtent1,
7630
0
    const metadata::ExtentPtr &intersectingExtent2) const {
7631
7632
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7633
7634
0
    if (sourceCRSAuthName == targetCRSAuthName &&
7635
0
        sourceCRSCode == targetCRSCode) {
7636
0
        return listTmp;
7637
0
    }
7638
0
    const auto sourceGeodCRS =
7639
0
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
7640
0
    const auto targetGeodCRS =
7641
0
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
7642
0
    if (!sourceGeodCRS || !targetGeodCRS) {
7643
0
        return listTmp;
7644
0
    }
7645
7646
0
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
7647
0
                                                const std::string &crsAuthName,
7648
0
                                                const std::string &crsCode) {
7649
        // Find all geodetic CRS that share the same datum as the CRS
7650
0
        SQLResultSet listCRS;
7651
7652
0
        const common::IdentifiedObject *obj = crs->datum().get();
7653
0
        if (obj == nullptr)
7654
0
            obj = crs->datumEnsemble().get();
7655
0
        assert(obj != nullptr);
7656
0
        const auto &ids = obj->identifiers();
7657
0
        std::string datumAuthName;
7658
0
        std::string datumCode;
7659
0
        if (!ids.empty()) {
7660
0
            const auto &id = ids.front();
7661
0
            datumAuthName = *(id->codeSpace());
7662
0
            datumCode = id->code();
7663
0
        } else {
7664
0
            const auto res =
7665
0
                d->run("SELECT datum_auth_name, datum_code FROM "
7666
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
7667
0
                       {crsAuthName, crsCode});
7668
0
            if (res.size() != 1) {
7669
0
                return listCRS;
7670
0
            }
7671
0
            const auto &row = res.front();
7672
0
            datumAuthName = row[0];
7673
0
            datumCode = row[1];
7674
0
        }
7675
7676
0
        listCRS =
7677
0
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
7678
0
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
7679
0
                   {datumAuthName, datumCode});
7680
0
        if (listCRS.empty()) {
7681
            // Can happen if the CRS is deprecated
7682
0
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
7683
0
        }
7684
0
        return listCRS;
7685
0
    };
7686
7687
0
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
7688
0
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
7689
0
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
7690
0
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
7691
0
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
7692
        // would happen only if we had CRS objects in the database without a
7693
        // link to a datum.
7694
0
        return listTmp;
7695
0
    }
7696
7697
0
    ListOfParams params;
7698
0
    const auto BuildSQLPart =
7699
0
        [this, &allowedAuthorities, &params, &listSourceCRS,
7700
0
         &listTargetCRS](bool isSourceCRS, bool selectOnTarget) {
7701
0
            std::string situation;
7702
0
            if (isSourceCRS)
7703
0
                situation = "src";
7704
0
            else
7705
0
                situation = "tgt";
7706
0
            if (selectOnTarget)
7707
0
                situation += "_is_tgt";
7708
0
            else
7709
0
                situation += "_is_src";
7710
0
            const std::string prefix1(selectOnTarget ? "source" : "target");
7711
0
            const std::string prefix2(selectOnTarget ? "target" : "source");
7712
0
            std::string sql("SELECT '");
7713
0
            sql += situation;
7714
0
            sql += "' as situation, v.table_name, v.auth_name, "
7715
0
                   "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
7716
0
                   "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
7717
0
                   "FROM coordinate_operation_view v "
7718
0
                   "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
7719
0
            sql += prefix1;
7720
0
            sql += "_crs_auth_name AND gcrs.code = ";
7721
0
            sql += prefix1;
7722
0
            sql += "_crs_code "
7723
7724
0
                   "LEFT JOIN usage u ON "
7725
0
                   "u.object_table_name = v.table_name AND "
7726
0
                   "u.object_auth_name = v.auth_name AND "
7727
0
                   "u.object_code = v.code "
7728
0
                   "LEFT JOIN extent a "
7729
0
                   "ON a.auth_name = u.extent_auth_name AND "
7730
0
                   "a.code = u.extent_code "
7731
0
                   "WHERE v.deprecated = 0 AND (";
7732
7733
0
            std::string cond;
7734
7735
0
            const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
7736
0
            for (const auto &row : list) {
7737
0
                if (!cond.empty())
7738
0
                    cond += " OR ";
7739
0
                cond += '(';
7740
0
                cond += prefix2;
7741
0
                cond += "_crs_auth_name = ? AND ";
7742
0
                cond += prefix2;
7743
0
                cond += "_crs_code = ?)";
7744
0
                params.emplace_back(row[0]);
7745
0
                params.emplace_back(row[1]);
7746
0
            }
7747
7748
0
            sql += cond;
7749
0
            sql += ") ";
7750
7751
0
            if (!allowedAuthorities.empty()) {
7752
0
                sql += "AND v.auth_name IN (";
7753
0
                for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7754
0
                    if (i > 0)
7755
0
                        sql += ',';
7756
0
                    sql += '?';
7757
0
                }
7758
0
                sql += ") ";
7759
0
                for (const auto &allowedAuthority : allowedAuthorities) {
7760
0
                    params.emplace_back(allowedAuthority);
7761
0
                }
7762
0
            }
7763
0
            if (d->hasAuthorityRestriction()) {
7764
0
                sql += "AND v.auth_name = ? ";
7765
0
                params.emplace_back(d->authority());
7766
0
            }
7767
7768
0
            return sql;
7769
0
        };
7770
7771
0
    std::string sql(BuildSQLPart(true, true));
7772
0
    sql += "UNION ALL ";
7773
0
    sql += BuildSQLPart(false, true);
7774
0
    sql += "UNION ALL ";
7775
0
    sql += BuildSQLPart(true, false);
7776
0
    sql += "UNION ALL ";
7777
0
    sql += BuildSQLPart(false, false);
7778
    // fprintf(stderr, "sql : %s\n", sql.c_str());
7779
7780
    // Find all operations that have as source/target CRS a CRS that
7781
    // share the same datum as the source or targetCRS
7782
0
    const auto res = d->run(sql, params);
7783
7784
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
7785
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
7786
7787
0
    for (const auto &row : res) {
7788
0
        try {
7789
0
            TrfmInfo trfm;
7790
0
            trfm.situation = row[0];
7791
0
            trfm.table_name = row[1];
7792
0
            trfm.auth_name = row[2];
7793
0
            trfm.code = row[3];
7794
0
            trfm.name = row[4];
7795
0
            const auto &datum_auth_name = row[5];
7796
0
            const auto &datum_code = row[6];
7797
0
            trfm.west = c_locale_stod(row[7]);
7798
0
            trfm.south = c_locale_stod(row[8]);
7799
0
            trfm.east = c_locale_stod(row[9]);
7800
0
            trfm.north = c_locale_stod(row[10]);
7801
0
            const std::string key =
7802
0
                std::string(datum_auth_name).append(":").append(datum_code);
7803
0
            if (trfm.situation == "src_is_tgt" ||
7804
0
                trfm.situation == "src_is_src")
7805
0
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
7806
0
            else
7807
0
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
7808
0
        } catch (const std::exception &) {
7809
0
        }
7810
0
    }
7811
7812
0
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
7813
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7814
0
        if (extent) {
7815
0
            const auto &geogExtent = extent->geographicElements();
7816
0
            if (geogExtent.size() == 1) {
7817
0
                auto bbox =
7818
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7819
0
                        geogExtent[0].get());
7820
0
                if (bbox) {
7821
0
                    const double south_lat = bbox->southBoundLatitude();
7822
0
                    const double west_lon = bbox->westBoundLongitude();
7823
0
                    const double north_lat = bbox->northBoundLatitude();
7824
0
                    const double east_lon = bbox->eastBoundLongitude();
7825
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7826
0
                        north_lat != 90.0 || east_lon != 180.0) {
7827
0
                        extraBbox.emplace_back(bbox);
7828
0
                    }
7829
0
                }
7830
0
            }
7831
0
        }
7832
0
    }
7833
7834
0
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
7835
0
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
7836
0
    std::map<std::string, TrfmInfo> setOfTransformations;
7837
7838
0
    const auto MakeKey = [](const TrfmInfo &trfm) {
7839
0
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
7840
0
    };
7841
7842
    // Find transformations that share a pivot datum, and do bbox filtering
7843
0
    for (const auto &kvSource : mapIntermDatumOfSource) {
7844
0
        const auto &listTrmfSource = kvSource.second;
7845
0
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
7846
0
        if (iter == mapIntermDatumOfTarget.end())
7847
0
            continue;
7848
7849
0
        const auto &listTrfmTarget = iter->second;
7850
0
        for (const auto &trfmSource : listTrmfSource) {
7851
0
            auto bbox1 = metadata::GeographicBoundingBox::create(
7852
0
                trfmSource.west, trfmSource.south, trfmSource.east,
7853
0
                trfmSource.north);
7854
0
            bool okBbox1 = true;
7855
0
            for (const auto bbox : extraBbox)
7856
0
                okBbox1 &= bbox->intersects(bbox1);
7857
0
            if (!okBbox1)
7858
0
                continue;
7859
7860
0
            const std::string key1 = MakeKey(trfmSource);
7861
7862
0
            for (const auto &trfmTarget : listTrfmTarget) {
7863
0
                auto bbox2 = metadata::GeographicBoundingBox::create(
7864
0
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
7865
0
                    trfmTarget.north);
7866
0
                if (!bbox1->intersects(bbox2))
7867
0
                    continue;
7868
0
                bool okBbox2 = true;
7869
0
                for (const auto bbox : extraBbox)
7870
0
                    okBbox2 &= bbox->intersects(bbox2);
7871
0
                if (!okBbox2)
7872
0
                    continue;
7873
7874
0
                operation::CoordinateOperationPtr op1;
7875
0
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
7876
0
                    auto op1NN = d->createFactory(trfmSource.auth_name)
7877
0
                                     ->createCoordinateOperation(
7878
0
                                         trfmSource.code, true,
7879
0
                                         usePROJAlternativeGridNames,
7880
0
                                         trfmSource.table_name);
7881
0
                    op1 = op1NN.as_nullable();
7882
0
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
7883
0
                                           sourceCRSCode, targetCRSAuthName,
7884
0
                                           targetCRSCode)) {
7885
0
                        op1.reset();
7886
0
                    }
7887
0
                    oMapTrfmKeyToOp[key1] = op1;
7888
0
                } else {
7889
0
                    op1 = oMapTrfmKeyToOp[key1];
7890
0
                }
7891
0
                if (op1 == nullptr)
7892
0
                    continue;
7893
7894
0
                const std::string key2 = MakeKey(trfmTarget);
7895
7896
0
                operation::CoordinateOperationPtr op2;
7897
0
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
7898
0
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
7899
0
                                     ->createCoordinateOperation(
7900
0
                                         trfmTarget.code, true,
7901
0
                                         usePROJAlternativeGridNames,
7902
0
                                         trfmTarget.table_name);
7903
0
                    op2 = op2NN.as_nullable();
7904
0
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
7905
0
                                           sourceCRSCode, targetCRSAuthName,
7906
0
                                           targetCRSCode)) {
7907
0
                        op2.reset();
7908
0
                    }
7909
0
                    oMapTrfmKeyToOp[key2] = op2;
7910
0
                } else {
7911
0
                    op2 = oMapTrfmKeyToOp[key2];
7912
0
                }
7913
0
                if (op2 == nullptr)
7914
0
                    continue;
7915
7916
0
                candidates.emplace_back(
7917
0
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
7918
0
                setOfTransformations[key1] = trfmSource;
7919
0
                setOfTransformations[key2] = trfmTarget;
7920
0
            }
7921
0
        }
7922
0
    }
7923
7924
0
    std::set<std::string> setSuperseded;
7925
0
    if (discardSuperseded && !setOfTransformations.empty()) {
7926
0
        std::string findSupersededSql(
7927
0
            "SELECT superseded_table_name, "
7928
0
            "superseded_auth_name, superseded_code, "
7929
0
            "replacement_auth_name, replacement_code "
7930
0
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
7931
0
        bool findSupersededFirstWhere = true;
7932
0
        ListOfParams findSupersededParams;
7933
7934
0
        const auto keyMapSupersession = [](const std::string &table_name,
7935
0
                                           const std::string &auth_name,
7936
0
                                           const std::string &code) {
7937
0
            return table_name + auth_name + code;
7938
0
        };
7939
7940
0
        std::set<std::pair<std::string, std::string>> setTransf;
7941
0
        for (const auto &kv : setOfTransformations) {
7942
0
            const auto &table = kv.second.table_name;
7943
0
            const auto &auth_name = kv.second.auth_name;
7944
0
            const auto &code = kv.second.code;
7945
7946
0
            if (!findSupersededFirstWhere)
7947
0
                findSupersededSql += " OR ";
7948
0
            findSupersededFirstWhere = false;
7949
0
            findSupersededSql +=
7950
0
                "(superseded_table_name = ? AND replacement_table_name = "
7951
0
                "superseded_table_name AND superseded_auth_name = ? AND "
7952
0
                "superseded_code = ?)";
7953
0
            findSupersededParams.push_back(table);
7954
0
            findSupersededParams.push_back(auth_name);
7955
0
            findSupersededParams.push_back(code);
7956
7957
0
            setTransf.insert(
7958
0
                std::pair<std::string, std::string>(auth_name, code));
7959
0
        }
7960
0
        findSupersededSql += ')';
7961
7962
0
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
7963
0
            mapSupersession;
7964
7965
0
        const auto resSuperseded =
7966
0
            d->run(findSupersededSql, findSupersededParams);
7967
0
        for (const auto &row : resSuperseded) {
7968
0
            const auto &superseded_table_name = row[0];
7969
0
            const auto &superseded_auth_name = row[1];
7970
0
            const auto &superseded_code = row[2];
7971
0
            const auto &replacement_auth_name = row[3];
7972
0
            const auto &replacement_code = row[4];
7973
0
            mapSupersession[keyMapSupersession(superseded_table_name,
7974
0
                                               superseded_auth_name,
7975
0
                                               superseded_code)]
7976
0
                .push_back(std::pair<std::string, std::string>(
7977
0
                    replacement_auth_name, replacement_code));
7978
0
        }
7979
7980
0
        for (const auto &kv : setOfTransformations) {
7981
0
            const auto &table = kv.second.table_name;
7982
0
            const auto &auth_name = kv.second.auth_name;
7983
0
            const auto &code = kv.second.code;
7984
7985
0
            const auto iter = mapSupersession.find(
7986
0
                keyMapSupersession(table, auth_name, code));
7987
0
            if (iter != mapSupersession.end()) {
7988
0
                bool foundReplacement = false;
7989
0
                for (const auto &replacement : iter->second) {
7990
0
                    const auto &replacement_auth_name = replacement.first;
7991
0
                    const auto &replacement_code = replacement.second;
7992
0
                    if (setTransf.find(std::pair<std::string, std::string>(
7993
0
                            replacement_auth_name, replacement_code)) !=
7994
0
                        setTransf.end()) {
7995
                        // Skip transformations that are superseded by others
7996
                        // that got
7997
                        // returned in the result set.
7998
0
                        foundReplacement = true;
7999
0
                        break;
8000
0
                    }
8001
0
                }
8002
0
                if (foundReplacement) {
8003
0
                    setSuperseded.insert(kv.first);
8004
0
                }
8005
0
            }
8006
0
        }
8007
0
    }
8008
8009
0
    auto opFactory = operation::CoordinateOperationFactory::create();
8010
0
    for (const auto &pair : candidates) {
8011
0
        const auto &trfmSource = pair.first;
8012
0
        const auto &trfmTarget = pair.second;
8013
0
        const std::string key1 = MakeKey(trfmSource);
8014
0
        const std::string key2 = MakeKey(trfmTarget);
8015
0
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8016
0
            setSuperseded.find(key2) != setSuperseded.end()) {
8017
0
            continue;
8018
0
        }
8019
0
        auto op1 = oMapTrfmKeyToOp[key1];
8020
0
        auto op2 = oMapTrfmKeyToOp[key2];
8021
0
        auto op1NN = NN_NO_CHECK(op1);
8022
0
        auto op2NN = NN_NO_CHECK(op2);
8023
0
        if (trfmSource.situation == "src_is_tgt")
8024
0
            op1NN = op1NN->inverse();
8025
0
        if (trfmTarget.situation == "tgt_is_src")
8026
0
            op2NN = op2NN->inverse();
8027
8028
0
        const auto &op1Source = op1NN->sourceCRS();
8029
0
        const auto &op1Target = op1NN->targetCRS();
8030
0
        const auto &op2Source = op2NN->sourceCRS();
8031
0
        const auto &op2Target = op2NN->targetCRS();
8032
0
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8033
0
            continue;
8034
0
        }
8035
8036
0
        std::vector<operation::CoordinateOperationNNPtr> steps;
8037
8038
0
        if (!sourceCRS->isEquivalentTo(
8039
0
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8040
0
            auto opFirst =
8041
0
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8042
0
            assert(opFirst);
8043
0
            steps.emplace_back(NN_NO_CHECK(opFirst));
8044
0
        }
8045
8046
0
        steps.emplace_back(op1NN);
8047
8048
0
        if (!op1Target->isEquivalentTo(
8049
0
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8050
0
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8051
0
                                                       NN_NO_CHECK(op2Source));
8052
0
            assert(opMiddle);
8053
0
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8054
0
        }
8055
8056
0
        steps.emplace_back(op2NN);
8057
8058
0
        if (!op2Target->isEquivalentTo(
8059
0
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8060
0
            auto opLast =
8061
0
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8062
0
            assert(opLast);
8063
0
            steps.emplace_back(NN_NO_CHECK(opLast));
8064
0
        }
8065
8066
0
        listTmp.emplace_back(
8067
0
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8068
0
                                                                    false));
8069
0
    }
8070
8071
0
    std::vector<operation::CoordinateOperationNNPtr> list;
8072
0
    for (const auto &op : listTmp) {
8073
0
        if (!discardIfMissingGrid ||
8074
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8075
0
            list.emplace_back(op);
8076
0
        }
8077
0
    }
8078
8079
0
    return list;
8080
0
}
8081
8082
//! @endcond
8083
8084
// ---------------------------------------------------------------------------
8085
8086
/** \brief Returns the authority name associated to this factory.
8087
 * @return name.
8088
 */
8089
0
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8090
0
    return d->authority();
8091
0
}
8092
8093
// ---------------------------------------------------------------------------
8094
8095
/** \brief Returns the set of authority codes of the given object type.
8096
 *
8097
 * @param type Object type.
8098
 * @param allowDeprecated whether we should return deprecated objects as well.
8099
 * @return the set of authority codes for spatial reference objects of the given
8100
 * type
8101
 * @throw FactoryException
8102
 */
8103
std::set<std::string>
8104
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8105
0
                                    bool allowDeprecated) const {
8106
0
    std::string sql;
8107
0
    switch (type) {
8108
0
    case ObjectType::PRIME_MERIDIAN:
8109
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8110
0
        break;
8111
0
    case ObjectType::ELLIPSOID:
8112
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8113
0
        break;
8114
0
    case ObjectType::DATUM:
8115
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8116
0
              "('geodetic_datum', 'vertical_datum') AND ";
8117
0
        break;
8118
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8119
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8120
0
        break;
8121
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8122
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8123
0
              "frame_reference_epoch IS NOT NULL AND ";
8124
0
        break;
8125
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8126
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8127
0
        break;
8128
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8129
0
        sql = "SELECT code FROM vertical_datum WHERE "
8130
0
              "frame_reference_epoch IS NOT NULL AND ";
8131
0
        break;
8132
0
    case ObjectType::CRS:
8133
0
        sql = "SELECT code FROM crs_view WHERE ";
8134
0
        break;
8135
0
    case ObjectType::GEODETIC_CRS:
8136
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8137
0
        break;
8138
0
    case ObjectType::GEOCENTRIC_CRS:
8139
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8140
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8141
0
        break;
8142
0
    case ObjectType::GEOGRAPHIC_CRS:
8143
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8144
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8145
0
        break;
8146
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8147
0
        sql =
8148
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8149
0
            " AND ";
8150
0
        break;
8151
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8152
0
        sql =
8153
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8154
0
            " AND ";
8155
0
        break;
8156
0
    case ObjectType::VERTICAL_CRS:
8157
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8158
0
        break;
8159
0
    case ObjectType::PROJECTED_CRS:
8160
0
        sql = "SELECT code FROM projected_crs WHERE ";
8161
0
        break;
8162
0
    case ObjectType::COMPOUND_CRS:
8163
0
        sql = "SELECT code FROM compound_crs WHERE ";
8164
0
        break;
8165
0
    case ObjectType::COORDINATE_OPERATION:
8166
0
        sql =
8167
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8168
0
        break;
8169
0
    case ObjectType::CONVERSION:
8170
0
        sql = "SELECT code FROM conversion WHERE ";
8171
0
        break;
8172
0
    case ObjectType::TRANSFORMATION:
8173
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8174
0
              "'concatenated_operation' AND ";
8175
0
        break;
8176
0
    case ObjectType::CONCATENATED_OPERATION:
8177
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8178
0
        break;
8179
0
    case ObjectType::DATUM_ENSEMBLE:
8180
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8181
0
              "('geodetic_datum', 'vertical_datum') AND "
8182
0
              "type = 'ensemble' AND ";
8183
0
        break;
8184
0
    }
8185
8186
0
    sql += "auth_name = ?";
8187
0
    if (!allowDeprecated) {
8188
0
        sql += " AND deprecated = 0";
8189
0
    }
8190
8191
0
    auto res = d->run(sql, {d->authority()});
8192
0
    std::set<std::string> set;
8193
0
    for (const auto &row : res) {
8194
0
        set.insert(row[0]);
8195
0
    }
8196
0
    return set;
8197
0
}
8198
8199
// ---------------------------------------------------------------------------
8200
8201
/** \brief Gets a description of the object corresponding to a code.
8202
 *
8203
 * \note In case of several objects of different types with the same code,
8204
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8205
 * will be selected.
8206
 *
8207
 * @param code Object code allocated by authority. (e.g. "4326")
8208
 * @return description.
8209
 * @throw NoSuchAuthorityCodeException
8210
 * @throw FactoryException
8211
 */
8212
std::string
8213
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8214
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8215
0
               "AND code = ? ORDER BY table_name";
8216
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8217
0
    if (sqlRes.empty()) {
8218
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8219
0
                                           code);
8220
0
    }
8221
0
    std::string text;
8222
0
    for (const auto &row : sqlRes) {
8223
0
        const auto &tableName = row[1];
8224
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8225
0
            tableName == "vertical_crs" || tableName == "compound_crs") {
8226
0
            return row[0];
8227
0
        } else if (text.empty()) {
8228
0
            text = row[0];
8229
0
        }
8230
0
    }
8231
0
    return text;
8232
0
}
8233
8234
// ---------------------------------------------------------------------------
8235
8236
/** \brief Return a list of information on CRS objects
8237
 *
8238
 * This is functionally equivalent of listing the codes from an authority,
8239
 * instantiating
8240
 * a CRS object for each of them and getting the information from this CRS
8241
 * object, but this implementation has much less overhead.
8242
 *
8243
 * @throw FactoryException
8244
 */
8245
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8246
8247
0
    const auto getSqlArea = [](const char *table_name) {
8248
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8249
0
        sql += table_name;
8250
0
        sql += "' AND "
8251
0
               "u.object_auth_name = c.auth_name AND "
8252
0
               "u.object_code = c.code "
8253
0
               "LEFT JOIN extent a "
8254
0
               "ON a.auth_name = u.extent_auth_name AND "
8255
0
               "a.code = u.extent_code ";
8256
0
        return sql;
8257
0
    };
8258
8259
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8260
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8261
0
        sql += crs_alias;
8262
0
        sql += ".datum_auth_name AND gd.code = ";
8263
0
        sql += crs_alias;
8264
0
        sql += ".datum_code "
8265
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8266
0
               "AND e.code = gd.ellipsoid_code "
8267
0
               "LEFT JOIN celestial_body cb ON "
8268
0
               "cb.auth_name = e.celestial_body_auth_name "
8269
0
               "AND cb.code = e.celestial_body_code ";
8270
0
        return sql;
8271
0
    };
8272
8273
0
    std::string sql = "SELECT * FROM ("
8274
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8275
0
                      "c.deprecated, "
8276
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8277
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8278
0
    sql += getSqlArea("geodetic_crs");
8279
0
    sql += getJoinCelestialBody("c");
8280
0
    ListOfParams params;
8281
0
    if (d->hasAuthorityRestriction()) {
8282
0
        sql += "WHERE c.auth_name = ? ";
8283
0
        params.emplace_back(d->authority());
8284
0
    }
8285
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8286
0
           "c.deprecated, "
8287
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8288
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8289
0
           "projected_crs c "
8290
0
           "LEFT JOIN conversion_table conv ON "
8291
0
           "c.conversion_auth_name = conv.auth_name AND "
8292
0
           "c.conversion_code = conv.code "
8293
0
           "LEFT JOIN conversion_method cm ON "
8294
0
           "conv.method_auth_name = cm.auth_name AND "
8295
0
           "conv.method_code = cm.code "
8296
0
           "LEFT JOIN geodetic_crs gcrs ON "
8297
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
8298
0
           "AND gcrs.code = c.geodetic_crs_code ";
8299
0
    sql += getSqlArea("projected_crs");
8300
0
    sql += getJoinCelestialBody("gcrs");
8301
0
    if (d->hasAuthorityRestriction()) {
8302
0
        sql += "WHERE c.auth_name = ? ";
8303
0
        params.emplace_back(d->authority());
8304
0
    }
8305
    // FIXME: we can't handle non-EARTH vertical CRS for now
8306
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
8307
0
           "c.deprecated, "
8308
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8309
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
8310
0
    sql += getSqlArea("vertical_crs");
8311
0
    if (d->hasAuthorityRestriction()) {
8312
0
        sql += "WHERE c.auth_name = ? ";
8313
0
        params.emplace_back(d->authority());
8314
0
    }
8315
    // FIXME: we can't handle non-EARTH compound CRS for now
8316
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
8317
0
           "c.deprecated, "
8318
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8319
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
8320
0
    sql += getSqlArea("compound_crs");
8321
0
    if (d->hasAuthorityRestriction()) {
8322
0
        sql += "WHERE c.auth_name = ? ";
8323
0
        params.emplace_back(d->authority());
8324
0
    }
8325
0
    sql += ") r ORDER BY auth_name, code";
8326
0
    auto sqlRes = d->run(sql, params);
8327
0
    std::list<AuthorityFactory::CRSInfo> res;
8328
0
    for (const auto &row : sqlRes) {
8329
0
        AuthorityFactory::CRSInfo info;
8330
0
        info.authName = row[0];
8331
0
        info.code = row[1];
8332
0
        info.name = row[2];
8333
0
        const auto &type = row[3];
8334
0
        if (type == GEOG_2D) {
8335
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
8336
0
        } else if (type == GEOG_3D) {
8337
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
8338
0
        } else if (type == GEOCENTRIC) {
8339
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
8340
0
        } else if (type == OTHER) {
8341
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
8342
0
        } else if (type == PROJECTED) {
8343
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
8344
0
        } else if (type == VERTICAL) {
8345
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
8346
0
        } else if (type == COMPOUND) {
8347
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
8348
0
        }
8349
0
        info.deprecated = row[4] == "1";
8350
0
        if (row[5].empty()) {
8351
0
            info.bbox_valid = false;
8352
0
        } else {
8353
0
            info.bbox_valid = true;
8354
0
            info.west_lon_degree = c_locale_stod(row[5]);
8355
0
            info.south_lat_degree = c_locale_stod(row[6]);
8356
0
            info.east_lon_degree = c_locale_stod(row[7]);
8357
0
            info.north_lat_degree = c_locale_stod(row[8]);
8358
0
        }
8359
0
        info.areaName = row[9];
8360
0
        info.projectionMethodName = row[10];
8361
0
        info.celestialBodyName = row[11];
8362
0
        res.emplace_back(info);
8363
0
    }
8364
0
    return res;
8365
0
}
8366
8367
// ---------------------------------------------------------------------------
8368
8369
//! @cond Doxygen_Suppress
8370
AuthorityFactory::UnitInfo::UnitInfo()
8371
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
8372
0
      deprecated{} {}
8373
//! @endcond
8374
8375
// ---------------------------------------------------------------------------
8376
8377
//! @cond Doxygen_Suppress
8378
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
8379
//! @endcond
8380
8381
// ---------------------------------------------------------------------------
8382
8383
/** \brief Return the list of units.
8384
 * @throw FactoryException
8385
 *
8386
 * @since 7.1
8387
 */
8388
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
8389
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
8390
0
                      "proj_short_name, deprecated FROM unit_of_measure";
8391
0
    ListOfParams params;
8392
0
    if (d->hasAuthorityRestriction()) {
8393
0
        sql += " WHERE auth_name = ?";
8394
0
        params.emplace_back(d->authority());
8395
0
    }
8396
0
    sql += " ORDER BY auth_name, code";
8397
8398
0
    auto sqlRes = d->run(sql, params);
8399
0
    std::list<AuthorityFactory::UnitInfo> res;
8400
0
    for (const auto &row : sqlRes) {
8401
0
        AuthorityFactory::UnitInfo info;
8402
0
        info.authName = row[0];
8403
0
        info.code = row[1];
8404
0
        info.name = row[2];
8405
0
        const std::string &raw_category(row[3]);
8406
0
        if (raw_category == "length") {
8407
0
            info.category = info.name.find(" per ") != std::string::npos
8408
0
                                ? "linear_per_time"
8409
0
                                : "linear";
8410
0
        } else if (raw_category == "angle") {
8411
0
            info.category = info.name.find(" per ") != std::string::npos
8412
0
                                ? "angular_per_time"
8413
0
                                : "angular";
8414
0
        } else if (raw_category == "scale") {
8415
0
            info.category =
8416
0
                info.name.find(" per year") != std::string::npos ||
8417
0
                        info.name.find(" per second") != std::string::npos
8418
0
                    ? "scale_per_time"
8419
0
                    : "scale";
8420
0
        } else {
8421
0
            info.category = raw_category;
8422
0
        }
8423
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
8424
0
        info.projShortName = row[5];
8425
0
        info.deprecated = row[6] == "1";
8426
0
        res.emplace_back(info);
8427
0
    }
8428
0
    return res;
8429
0
}
8430
8431
// ---------------------------------------------------------------------------
8432
8433
/** \brief Return the list of celestial bodies.
8434
 * @throw FactoryException
8435
 *
8436
 * @since 8.1
8437
 */
8438
std::list<AuthorityFactory::CelestialBodyInfo>
8439
0
AuthorityFactory::getCelestialBodyList() const {
8440
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
8441
0
    ListOfParams params;
8442
0
    if (d->hasAuthorityRestriction()) {
8443
0
        sql += " WHERE auth_name = ?";
8444
0
        params.emplace_back(d->authority());
8445
0
    }
8446
0
    sql += " ORDER BY auth_name, name";
8447
8448
0
    auto sqlRes = d->run(sql, params);
8449
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
8450
0
    for (const auto &row : sqlRes) {
8451
0
        AuthorityFactory::CelestialBodyInfo info;
8452
0
        info.authName = row[0];
8453
0
        info.name = row[1];
8454
0
        res.emplace_back(info);
8455
0
    }
8456
0
    return res;
8457
0
}
8458
8459
// ---------------------------------------------------------------------------
8460
8461
/** \brief Gets the official name from a possibly alias name.
8462
 *
8463
 * @param aliasedName Alias name.
8464
 * @param tableName Table name/category. Can help in case of ambiguities.
8465
 * Or empty otherwise.
8466
 * @param source Source of the alias. Can help in case of ambiguities.
8467
 * Or empty otherwise.
8468
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
8469
 * the alt_name column of the alias_name table should be done with using
8470
 * metadata::Identifier::isEquivalentName() rather than strict string
8471
 * comparison;
8472
 * @param outTableName Table name in which the official name has been found.
8473
 * @param outAuthName Authority name of the official name that has been found.
8474
 * @param outCode Code of the official name that has been found.
8475
 * @return official name (or empty if not found).
8476
 * @throw FactoryException
8477
 */
8478
std::string AuthorityFactory::getOfficialNameFromAlias(
8479
    const std::string &aliasedName, const std::string &tableName,
8480
    const std::string &source, bool tryEquivalentNameSpelling,
8481
    std::string &outTableName, std::string &outAuthName,
8482
0
    std::string &outCode) const {
8483
8484
0
    if (tryEquivalentNameSpelling) {
8485
0
        std::string sql(
8486
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
8487
0
        ListOfParams params;
8488
0
        if (!tableName.empty()) {
8489
0
            sql += " WHERE table_name = ?";
8490
0
            params.push_back(tableName);
8491
0
        }
8492
0
        if (!source.empty()) {
8493
0
            if (!tableName.empty()) {
8494
0
                sql += " AND ";
8495
0
            } else {
8496
0
                sql += " WHERE ";
8497
0
            }
8498
0
            sql += "source = ?";
8499
0
            params.push_back(source);
8500
0
        }
8501
0
        auto res = d->run(sql, params);
8502
0
        if (res.empty()) {
8503
0
            return std::string();
8504
0
        }
8505
0
        for (const auto &row : res) {
8506
0
            const auto &alt_name = row[3];
8507
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
8508
0
                                                       aliasedName.c_str())) {
8509
0
                outTableName = row[0];
8510
0
                outAuthName = row[1];
8511
0
                outCode = row[2];
8512
0
                sql = "SELECT name FROM \"";
8513
0
                sql += replaceAll(outTableName, "\"", "\"\"");
8514
0
                sql += "\" WHERE auth_name = ? AND code = ?";
8515
0
                res = d->run(sql, {outAuthName, outCode});
8516
0
                if (res.empty()) { // shouldn't happen normally
8517
0
                    return std::string();
8518
0
                }
8519
0
                return res.front()[0];
8520
0
            }
8521
0
        }
8522
0
        return std::string();
8523
0
    } else {
8524
0
        std::string sql(
8525
0
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
8526
0
            "alt_name = ?");
8527
0
        ListOfParams params{aliasedName};
8528
0
        if (!tableName.empty()) {
8529
0
            sql += " AND table_name = ?";
8530
0
            params.push_back(tableName);
8531
0
        }
8532
0
        if (!source.empty()) {
8533
0
            sql += " AND source = ?";
8534
0
            params.push_back(source);
8535
0
        }
8536
0
        auto res = d->run(sql, params);
8537
0
        if (res.empty()) {
8538
0
            return std::string();
8539
0
        }
8540
8541
0
        params.clear();
8542
0
        sql.clear();
8543
0
        bool first = true;
8544
0
        for (const auto &row : res) {
8545
0
            if (!first)
8546
0
                sql += " UNION ALL ";
8547
0
            first = false;
8548
0
            outTableName = row[0];
8549
0
            outAuthName = row[1];
8550
0
            outCode = row[2];
8551
0
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
8552
0
                   "FROM \"";
8553
0
            sql += replaceAll(outTableName, "\"", "\"\"");
8554
0
            sql += "\" WHERE auth_name = ? AND code = ?";
8555
0
            params.emplace_back(outTableName);
8556
0
            params.emplace_back(outAuthName);
8557
0
            params.emplace_back(outCode);
8558
0
        }
8559
0
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
8560
0
              ") x ORDER BY deprecated LIMIT 1";
8561
0
        res = d->run(sql, params);
8562
0
        if (res.empty()) { // shouldn't happen normally
8563
0
            return std::string();
8564
0
        }
8565
0
        const auto &row = res.front();
8566
0
        outTableName = row[1];
8567
0
        outAuthName = row[2];
8568
0
        outCode = row[3];
8569
0
        return row[0];
8570
0
    }
8571
0
}
8572
8573
// ---------------------------------------------------------------------------
8574
8575
/** \brief Return a list of objects, identified by their name
8576
 *
8577
 * @param searchedName Searched name. Must be at least 2 character long.
8578
 * @param allowedObjectTypes List of object types into which to search. If
8579
 * empty, all object types will be searched.
8580
 * @param approximateMatch Whether approximate name identification is allowed.
8581
 * @param limitResultCount Maximum number of results to return.
8582
 * Or 0 for unlimited.
8583
 * @return list of matched objects.
8584
 * @throw FactoryException
8585
 */
8586
std::list<common::IdentifiedObjectNNPtr>
8587
AuthorityFactory::createObjectsFromName(
8588
    const std::string &searchedName,
8589
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
8590
0
    size_t limitResultCount) const {
8591
0
    std::list<common::IdentifiedObjectNNPtr> res;
8592
0
    const auto resTmp(createObjectsFromNameEx(
8593
0
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
8594
0
    for (const auto &pair : resTmp) {
8595
0
        res.emplace_back(pair.first);
8596
0
    }
8597
0
    return res;
8598
0
}
8599
8600
// ---------------------------------------------------------------------------
8601
8602
//! @cond Doxygen_Suppress
8603
8604
/** \brief Return a list of objects, identifier by their name, with the name
8605
 * on which the match occurred.
8606
 *
8607
 * The name on which the match occurred might be different from the object name,
8608
 * if the match has been done on an alias name of that object.
8609
 *
8610
 * @param searchedName Searched name. Must be at least 2 character long.
8611
 * @param allowedObjectTypes List of object types into which to search. If
8612
 * empty, all object types will be searched.
8613
 * @param approximateMatch Whether approximate name identification is allowed.
8614
 * @param limitResultCount Maximum number of results to return.
8615
 * Or 0 for unlimited.
8616
 * @return list of matched objects.
8617
 * @throw FactoryException
8618
 */
8619
std::list<AuthorityFactory::PairObjectName>
8620
AuthorityFactory::createObjectsFromNameEx(
8621
    const std::string &searchedName,
8622
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
8623
0
    size_t limitResultCount) const {
8624
0
    std::string searchedNameWithoutDeprecated(searchedName);
8625
0
    bool deprecated = false;
8626
0
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
8627
0
        deprecated = true;
8628
0
        searchedNameWithoutDeprecated.resize(
8629
0
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
8630
0
    }
8631
8632
0
    const std::string canonicalizedSearchedName(
8633
0
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
8634
0
    if (canonicalizedSearchedName.size() <= 1) {
8635
0
        return {};
8636
0
    }
8637
8638
0
    std::string sql(
8639
0
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
8640
0
        "FROM (");
8641
8642
0
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
8643
0
                                             &searchedName]() {
8644
0
        typedef std::pair<std::string, std::string> TableType;
8645
0
        std::list<TableType> res;
8646
        // Hide ESRI D_ vertical datums
8647
0
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
8648
0
        if (allowedObjectTypes.empty()) {
8649
0
            for (const auto &tableName :
8650
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
8651
0
                  "vertical_datum", "geodetic_crs", "projected_crs",
8652
0
                  "vertical_crs", "compound_crs", "conversion",
8653
0
                  "helmert_transformation", "grid_transformation",
8654
0
                  "other_transformation", "concatenated_operation"}) {
8655
0
                if (!(startsWithDUnderscore &&
8656
0
                      strcmp(tableName, "vertical_datum") == 0)) {
8657
0
                    res.emplace_back(TableType(tableName, std::string()));
8658
0
                }
8659
0
            }
8660
0
        } else {
8661
0
            for (const auto type : allowedObjectTypes) {
8662
0
                switch (type) {
8663
0
                case ObjectType::PRIME_MERIDIAN:
8664
0
                    res.emplace_back(
8665
0
                        TableType("prime_meridian", std::string()));
8666
0
                    break;
8667
0
                case ObjectType::ELLIPSOID:
8668
0
                    res.emplace_back(TableType("ellipsoid", std::string()));
8669
0
                    break;
8670
0
                case ObjectType::DATUM:
8671
0
                    res.emplace_back(
8672
0
                        TableType("geodetic_datum", std::string()));
8673
0
                    if (!startsWithDUnderscore) {
8674
0
                        res.emplace_back(
8675
0
                            TableType("vertical_datum", std::string()));
8676
0
                    }
8677
0
                    break;
8678
0
                case ObjectType::GEODETIC_REFERENCE_FRAME:
8679
0
                    res.emplace_back(
8680
0
                        TableType("geodetic_datum", std::string()));
8681
0
                    break;
8682
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8683
0
                    res.emplace_back(
8684
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
8685
0
                    break;
8686
0
                case ObjectType::VERTICAL_REFERENCE_FRAME:
8687
0
                    res.emplace_back(
8688
0
                        TableType("vertical_datum", std::string()));
8689
0
                    break;
8690
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8691
0
                    res.emplace_back(
8692
0
                        TableType("vertical_datum", "frame_reference_epoch"));
8693
0
                    break;
8694
0
                case ObjectType::CRS:
8695
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
8696
0
                    res.emplace_back(TableType("projected_crs", std::string()));
8697
0
                    res.emplace_back(TableType("vertical_crs", std::string()));
8698
0
                    res.emplace_back(TableType("compound_crs", std::string()));
8699
0
                    break;
8700
0
                case ObjectType::GEODETIC_CRS:
8701
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
8702
0
                    break;
8703
0
                case ObjectType::GEOCENTRIC_CRS:
8704
0
                    res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
8705
0
                    break;
8706
0
                case ObjectType::GEOGRAPHIC_CRS:
8707
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
8708
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
8709
0
                    break;
8710
0
                case ObjectType::GEOGRAPHIC_2D_CRS:
8711
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
8712
0
                    break;
8713
0
                case ObjectType::GEOGRAPHIC_3D_CRS:
8714
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
8715
0
                    break;
8716
0
                case ObjectType::PROJECTED_CRS:
8717
0
                    res.emplace_back(TableType("projected_crs", std::string()));
8718
0
                    break;
8719
0
                case ObjectType::VERTICAL_CRS:
8720
0
                    res.emplace_back(TableType("vertical_crs", std::string()));
8721
0
                    break;
8722
0
                case ObjectType::COMPOUND_CRS:
8723
0
                    res.emplace_back(TableType("compound_crs", std::string()));
8724
0
                    break;
8725
0
                case ObjectType::COORDINATE_OPERATION:
8726
0
                    res.emplace_back(TableType("conversion", std::string()));
8727
0
                    res.emplace_back(
8728
0
                        TableType("helmert_transformation", std::string()));
8729
0
                    res.emplace_back(
8730
0
                        TableType("grid_transformation", std::string()));
8731
0
                    res.emplace_back(
8732
0
                        TableType("other_transformation", std::string()));
8733
0
                    res.emplace_back(
8734
0
                        TableType("concatenated_operation", std::string()));
8735
0
                    break;
8736
0
                case ObjectType::CONVERSION:
8737
0
                    res.emplace_back(TableType("conversion", std::string()));
8738
0
                    break;
8739
0
                case ObjectType::TRANSFORMATION:
8740
0
                    res.emplace_back(
8741
0
                        TableType("helmert_transformation", std::string()));
8742
0
                    res.emplace_back(
8743
0
                        TableType("grid_transformation", std::string()));
8744
0
                    res.emplace_back(
8745
0
                        TableType("other_transformation", std::string()));
8746
0
                    break;
8747
0
                case ObjectType::CONCATENATED_OPERATION:
8748
0
                    res.emplace_back(
8749
0
                        TableType("concatenated_operation", std::string()));
8750
0
                    break;
8751
0
                case ObjectType::DATUM_ENSEMBLE:
8752
0
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
8753
0
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
8754
0
                    break;
8755
0
                }
8756
0
            }
8757
0
        }
8758
0
        return res;
8759
0
    };
8760
8761
0
    bool datumEnsembleAllowed = false;
8762
0
    if (allowedObjectTypes.empty()) {
8763
0
        datumEnsembleAllowed = true;
8764
0
    } else {
8765
0
        for (const auto type : allowedObjectTypes) {
8766
0
            if (type == ObjectType::DATUM_ENSEMBLE) {
8767
0
                datumEnsembleAllowed = true;
8768
0
                break;
8769
0
            }
8770
0
        }
8771
0
    }
8772
8773
0
    const auto listTableNameType = getTableAndTypeConstraints();
8774
0
    bool first = true;
8775
0
    ListOfParams params;
8776
0
    for (const auto &tableNameTypePair : listTableNameType) {
8777
0
        if (!first) {
8778
0
            sql += " UNION ";
8779
0
        }
8780
0
        first = false;
8781
0
        sql += "SELECT '";
8782
0
        sql += tableNameTypePair.first;
8783
0
        sql += "' AS table_name, auth_name, code, name, deprecated, "
8784
0
               "0 AS is_alias FROM ";
8785
0
        sql += tableNameTypePair.first;
8786
0
        sql += " WHERE 1 = 1 ";
8787
0
        if (!tableNameTypePair.second.empty()) {
8788
0
            if (tableNameTypePair.second == "frame_reference_epoch") {
8789
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
8790
0
            } else if (tableNameTypePair.second == "ensemble") {
8791
0
                sql += "AND ensemble_accuracy IS NOT NULL ";
8792
0
            } else {
8793
0
                sql += "AND type = '";
8794
0
                sql += tableNameTypePair.second;
8795
0
                sql += "' ";
8796
0
            }
8797
0
        }
8798
0
        if (deprecated) {
8799
0
            sql += "AND deprecated = 1 ";
8800
0
        }
8801
0
        if (!approximateMatch) {
8802
0
            sql += "AND name = ? COLLATE NOCASE ";
8803
0
            params.push_back(searchedNameWithoutDeprecated);
8804
0
        }
8805
0
        if (d->hasAuthorityRestriction()) {
8806
0
            sql += "AND auth_name = ? ";
8807
0
            params.emplace_back(d->authority());
8808
0
        }
8809
8810
0
        sql += " UNION SELECT '";
8811
0
        sql += tableNameTypePair.first;
8812
0
        sql += "' AS table_name, "
8813
0
               "ov.auth_name AS auth_name, "
8814
0
               "ov.code AS code, a.alt_name AS name, "
8815
0
               "ov.deprecated AS deprecated, 1 as is_alias FROM ";
8816
0
        sql += tableNameTypePair.first;
8817
0
        sql += " ov "
8818
0
               "JOIN alias_name a ON "
8819
0
               "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
8820
0
               "a.table_name = '";
8821
0
        sql += tableNameTypePair.first;
8822
0
        sql += "' ";
8823
0
        if (!tableNameTypePair.second.empty()) {
8824
0
            if (tableNameTypePair.second == "frame_reference_epoch") {
8825
0
                sql += "AND ov.frame_reference_epoch IS NOT NULL ";
8826
0
            } else if (tableNameTypePair.second == "ensemble") {
8827
0
                sql += "AND ov.ensemble_accuracy IS NOT NULL ";
8828
0
            } else {
8829
0
                sql += "AND ov.type = '";
8830
0
                sql += tableNameTypePair.second;
8831
0
                sql += "' ";
8832
0
            }
8833
0
        }
8834
0
        if (deprecated) {
8835
0
            sql += "AND ov.deprecated = 1 ";
8836
0
        }
8837
0
        if (!approximateMatch) {
8838
0
            sql += "AND a.alt_name = ? COLLATE NOCASE ";
8839
0
            params.push_back(searchedNameWithoutDeprecated);
8840
0
        }
8841
0
        if (d->hasAuthorityRestriction()) {
8842
0
            sql += "AND ov.auth_name = ? ";
8843
0
            params.emplace_back(d->authority());
8844
0
        }
8845
0
    }
8846
8847
0
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
8848
0
    if (limitResultCount > 0 &&
8849
0
        limitResultCount <
8850
0
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
8851
0
        !approximateMatch) {
8852
0
        sql += " LIMIT ";
8853
0
        sql += toString(static_cast<int>(limitResultCount));
8854
0
    }
8855
8856
0
    std::list<PairObjectName> res;
8857
0
    std::set<std::pair<std::string, std::string>> setIdentified;
8858
8859
    // Querying geodetic datum is a super hot path when importing from WKT1
8860
    // so cache results.
8861
0
    if (allowedObjectTypes.size() == 1 &&
8862
0
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
8863
0
        approximateMatch && d->authority().empty()) {
8864
0
        auto &mapCanonicalizeGRFName =
8865
0
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
8866
0
        if (mapCanonicalizeGRFName.empty()) {
8867
0
            auto sqlRes = d->run(sql, params);
8868
0
            for (const auto &row : sqlRes) {
8869
0
                const auto &name = row[3];
8870
0
                const auto &deprecatedStr = row[4];
8871
0
                const auto canonicalizedName(
8872
0
                    metadata::Identifier::canonicalizeName(name));
8873
0
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
8874
0
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
8875
0
                    v.push_back(row);
8876
0
                }
8877
0
            }
8878
0
        }
8879
0
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
8880
0
        if (iter != mapCanonicalizeGRFName.end()) {
8881
0
            const auto &listOfRow = iter->second;
8882
0
            for (const auto &row : listOfRow) {
8883
0
                const auto &auth_name = row[1];
8884
0
                const auto &code = row[2];
8885
0
                const auto key =
8886
0
                    std::pair<std::string, std::string>(auth_name, code);
8887
0
                if (setIdentified.find(key) != setIdentified.end()) {
8888
0
                    continue;
8889
0
                }
8890
0
                setIdentified.insert(key);
8891
0
                auto factory = d->createFactory(auth_name);
8892
0
                const auto &name = row[3];
8893
0
                res.emplace_back(
8894
0
                    PairObjectName(factory->createGeodeticDatum(code), name));
8895
0
                if (limitResultCount > 0 && res.size() == limitResultCount) {
8896
0
                    break;
8897
0
                }
8898
0
            }
8899
0
        } else {
8900
0
            for (const auto &pair : mapCanonicalizeGRFName) {
8901
0
                const auto &listOfRow = pair.second;
8902
0
                for (const auto &row : listOfRow) {
8903
0
                    const auto &name = row[3];
8904
0
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
8905
0
                                 std::string::npos;
8906
0
                    if (!match) {
8907
0
                        const auto &canonicalizedName(pair.first);
8908
0
                        match = ci_find(canonicalizedName,
8909
0
                                        canonicalizedSearchedName) !=
8910
0
                                std::string::npos;
8911
0
                    }
8912
0
                    if (!match) {
8913
0
                        continue;
8914
0
                    }
8915
8916
0
                    const auto &auth_name = row[1];
8917
0
                    const auto &code = row[2];
8918
0
                    const auto key =
8919
0
                        std::pair<std::string, std::string>(auth_name, code);
8920
0
                    if (setIdentified.find(key) != setIdentified.end()) {
8921
0
                        continue;
8922
0
                    }
8923
0
                    setIdentified.insert(key);
8924
0
                    auto factory = d->createFactory(auth_name);
8925
0
                    res.emplace_back(PairObjectName(
8926
0
                        factory->createGeodeticDatum(code), name));
8927
0
                    if (limitResultCount > 0 &&
8928
0
                        res.size() == limitResultCount) {
8929
0
                        break;
8930
0
                    }
8931
0
                }
8932
0
                if (limitResultCount > 0 && res.size() == limitResultCount) {
8933
0
                    break;
8934
0
                }
8935
0
            }
8936
0
        }
8937
0
    } else {
8938
0
        auto sqlRes = d->run(sql, params);
8939
0
        bool isFirst = true;
8940
0
        bool firstIsDeprecated = false;
8941
0
        bool foundExactMatch = false;
8942
0
        std::size_t hashCodeFirstMatch = 0;
8943
0
        for (const auto &row : sqlRes) {
8944
0
            const auto &name = row[3];
8945
0
            if (approximateMatch) {
8946
0
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
8947
0
                             std::string::npos;
8948
0
                if (!match) {
8949
0
                    const auto canonicalizedName(
8950
0
                        metadata::Identifier::canonicalizeName(name));
8951
0
                    match =
8952
0
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
8953
0
                        std::string::npos;
8954
0
                }
8955
0
                if (!match) {
8956
0
                    continue;
8957
0
                }
8958
0
            }
8959
0
            const auto &table_name = row[0];
8960
0
            const auto &auth_name = row[1];
8961
0
            const auto &code = row[2];
8962
0
            const auto key =
8963
0
                std::pair<std::string, std::string>(auth_name, code);
8964
0
            if (setIdentified.find(key) != setIdentified.end()) {
8965
0
                continue;
8966
0
            }
8967
0
            setIdentified.insert(key);
8968
0
            const auto &deprecatedStr = row[4];
8969
0
            if (isFirst) {
8970
0
                firstIsDeprecated = deprecatedStr == "1";
8971
0
                isFirst = false;
8972
0
            }
8973
0
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
8974
0
                break;
8975
0
            }
8976
0
            auto factory = d->createFactory(auth_name);
8977
0
            auto getObject = [&factory, datumEnsembleAllowed](
8978
0
                                 const std::string &l_table_name,
8979
0
                                 const std::string &l_code)
8980
0
                -> common::IdentifiedObjectNNPtr {
8981
0
                if (l_table_name == "prime_meridian") {
8982
0
                    return factory->createPrimeMeridian(l_code);
8983
0
                } else if (l_table_name == "ellipsoid") {
8984
0
                    return factory->createEllipsoid(l_code);
8985
0
                } else if (l_table_name == "geodetic_datum") {
8986
0
                    if (datumEnsembleAllowed) {
8987
0
                        datum::GeodeticReferenceFramePtr datum;
8988
0
                        datum::DatumEnsemblePtr datumEnsemble;
8989
0
                        constexpr bool turnEnsembleAsDatum = false;
8990
0
                        factory->createGeodeticDatumOrEnsemble(
8991
0
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
8992
0
                        if (datum) {
8993
0
                            return NN_NO_CHECK(datum);
8994
0
                        }
8995
0
                        assert(datumEnsemble);
8996
0
                        return NN_NO_CHECK(datumEnsemble);
8997
0
                    }
8998
0
                    return factory->createGeodeticDatum(l_code);
8999
0
                } else if (l_table_name == "vertical_datum") {
9000
0
                    if (datumEnsembleAllowed) {
9001
0
                        datum::VerticalReferenceFramePtr datum;
9002
0
                        datum::DatumEnsemblePtr datumEnsemble;
9003
0
                        constexpr bool turnEnsembleAsDatum = false;
9004
0
                        factory->createVerticalDatumOrEnsemble(
9005
0
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9006
0
                        if (datum) {
9007
0
                            return NN_NO_CHECK(datum);
9008
0
                        }
9009
0
                        assert(datumEnsemble);
9010
0
                        return NN_NO_CHECK(datumEnsemble);
9011
0
                    }
9012
0
                    return factory->createVerticalDatum(l_code);
9013
0
                } else if (l_table_name == "geodetic_crs") {
9014
0
                    return factory->createGeodeticCRS(l_code);
9015
0
                } else if (l_table_name == "projected_crs") {
9016
0
                    return factory->createProjectedCRS(l_code);
9017
0
                } else if (l_table_name == "vertical_crs") {
9018
0
                    return factory->createVerticalCRS(l_code);
9019
0
                } else if (l_table_name == "compound_crs") {
9020
0
                    return factory->createCompoundCRS(l_code);
9021
0
                } else if (l_table_name == "conversion") {
9022
0
                    return factory->createConversion(l_code);
9023
0
                } else if (l_table_name == "grid_transformation" ||
9024
0
                           l_table_name == "helmert_transformation" ||
9025
0
                           l_table_name == "other_transformation" ||
9026
0
                           l_table_name == "concatenated_operation") {
9027
0
                    return factory->createCoordinateOperation(l_code, true);
9028
0
                }
9029
0
                throw std::runtime_error("Unsupported table_name");
9030
0
            };
9031
0
            const auto obj = getObject(table_name, code);
9032
0
            if (metadata::Identifier::canonicalizeName(obj->nameStr()) ==
9033
0
                canonicalizedSearchedName) {
9034
0
                foundExactMatch = true;
9035
0
            }
9036
9037
0
            const auto objPtr = obj.get();
9038
0
            if (res.empty()) {
9039
0
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9040
0
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9041
0
                hashCodeFirstMatch = 0;
9042
0
            }
9043
9044
0
            res.emplace_back(PairObjectName(obj, name));
9045
0
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9046
0
                break;
9047
0
            }
9048
0
        }
9049
9050
        // If we found a name that is an exact match, and all objects have the
9051
        // same type, and we are not in approximate mode, only keep the
9052
        // object(s) with the exact name match.
9053
0
        if (foundExactMatch && hashCodeFirstMatch != 0 && !approximateMatch) {
9054
0
            std::list<PairObjectName> resTmp;
9055
0
            for (const auto &pair : res) {
9056
0
                if (metadata::Identifier::canonicalizeName(
9057
0
                        pair.first->nameStr()) == canonicalizedSearchedName) {
9058
0
                    resTmp.emplace_back(pair);
9059
0
                }
9060
0
            }
9061
0
            res = std::move(resTmp);
9062
0
        }
9063
0
    }
9064
9065
0
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9066
0
        const auto &aName = a.first->nameStr();
9067
0
        const auto &bName = b.first->nameStr();
9068
0
        if (aName.size() < bName.size()) {
9069
0
            return true;
9070
0
        }
9071
0
        if (aName.size() > bName.size()) {
9072
0
            return false;
9073
0
        }
9074
9075
0
        const auto &aIds = a.first->identifiers();
9076
0
        const auto &bIds = b.first->identifiers();
9077
0
        if (aIds.size() < bIds.size()) {
9078
0
            return true;
9079
0
        }
9080
0
        if (aIds.size() > bIds.size()) {
9081
0
            return false;
9082
0
        }
9083
0
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9084
0
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9085
0
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9086
0
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9087
0
            if (codeSpaceComparison < 0) {
9088
0
                return true;
9089
0
            }
9090
0
            if (codeSpaceComparison > 0) {
9091
0
                return false;
9092
0
            }
9093
0
            const auto &aCode = aIds[idx]->code();
9094
0
            const auto &bCode = bIds[idx]->code();
9095
0
            const auto codeComparison = aCode.compare(bCode);
9096
0
            if (codeComparison < 0) {
9097
0
                return true;
9098
0
            }
9099
0
            if (codeComparison > 0) {
9100
0
                return false;
9101
0
            }
9102
0
        }
9103
0
        return strcmp(typeid(a.first.get()).name(),
9104
0
                      typeid(b.first.get()).name()) < 0;
9105
0
    };
9106
9107
0
    res.sort(sortLambda);
9108
9109
0
    return res;
9110
0
}
9111
//! @endcond
9112
9113
// ---------------------------------------------------------------------------
9114
9115
/** \brief Return a list of area of use from their name
9116
 *
9117
 * @param name Searched name.
9118
 * @param approximateMatch Whether approximate name identification is allowed.
9119
 * @return list of (auth_name, code) of matched objects.
9120
 * @throw FactoryException
9121
 */
9122
std::list<std::pair<std::string, std::string>>
9123
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9124
0
                                        bool approximateMatch) const {
9125
0
    std::string sql(
9126
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9127
0
    ListOfParams params;
9128
0
    if (d->hasAuthorityRestriction()) {
9129
0
        sql += " auth_name = ? AND ";
9130
0
        params.emplace_back(d->authority());
9131
0
    }
9132
0
    sql += "name LIKE ?";
9133
0
    if (!approximateMatch) {
9134
0
        params.push_back(name);
9135
0
    } else {
9136
0
        params.push_back('%' + name + '%');
9137
0
    }
9138
0
    auto sqlRes = d->run(sql, params);
9139
0
    std::list<std::pair<std::string, std::string>> res;
9140
0
    for (const auto &row : sqlRes) {
9141
0
        res.emplace_back(row[0], row[1]);
9142
0
    }
9143
0
    return res;
9144
0
}
9145
9146
// ---------------------------------------------------------------------------
9147
9148
//! @cond Doxygen_Suppress
9149
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9150
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9151
0
    std::string sql(
9152
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9153
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9154
0
        "((semi_minor_axis IS NOT NULL AND "
9155
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9156
0
        "((inv_flattening IS NOT NULL AND "
9157
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9158
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9159
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9160
0
                        ellipsoid->computedInverseFlattening()};
9161
0
    auto sqlRes = d->run(sql, params);
9162
0
    std::list<datum::EllipsoidNNPtr> res;
9163
0
    for (const auto &row : sqlRes) {
9164
0
        const auto &auth_name = row[0];
9165
0
        const auto &code = row[1];
9166
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9167
0
    }
9168
0
    return res;
9169
0
}
9170
//! @endcond
9171
9172
// ---------------------------------------------------------------------------
9173
9174
//! @cond Doxygen_Suppress
9175
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9176
    const std::string &datum_auth_name, const std::string &datum_code,
9177
0
    const std::string &geodetic_crs_type) const {
9178
0
    std::string sql(
9179
0
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9180
0
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9181
0
    ListOfParams params{datum_auth_name, datum_code};
9182
0
    if (d->hasAuthorityRestriction()) {
9183
0
        sql += " AND auth_name = ?";
9184
0
        params.emplace_back(d->authority());
9185
0
    }
9186
0
    if (!geodetic_crs_type.empty()) {
9187
0
        sql += " AND type = ?";
9188
0
        params.emplace_back(geodetic_crs_type);
9189
0
    }
9190
0
    sql += " ORDER BY auth_name, code";
9191
0
    auto sqlRes = d->run(sql, params);
9192
0
    std::list<crs::GeodeticCRSNNPtr> res;
9193
0
    for (const auto &row : sqlRes) {
9194
0
        const auto &auth_name = row[0];
9195
0
        const auto &code = row[1];
9196
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9197
0
    }
9198
0
    return res;
9199
0
}
9200
//! @endcond
9201
9202
// ---------------------------------------------------------------------------
9203
9204
//! @cond Doxygen_Suppress
9205
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9206
    const datum::GeodeticReferenceFrameNNPtr &datum,
9207
    const std::string &preferredAuthName,
9208
0
    const std::string &geodetic_crs_type) const {
9209
0
    std::list<crs::GeodeticCRSNNPtr> candidates;
9210
0
    const auto &ids = datum->identifiers();
9211
0
    const auto &datumName = datum->nameStr();
9212
0
    if (!ids.empty()) {
9213
0
        for (const auto &id : ids) {
9214
0
            const auto &authName = *(id->codeSpace());
9215
0
            const auto &code = id->code();
9216
0
            if (!authName.empty()) {
9217
0
                const auto tmpFactory =
9218
0
                    (preferredAuthName == authName)
9219
0
                        ? create(databaseContext(), authName)
9220
0
                        : NN_NO_CHECK(d->getSharedFromThis());
9221
0
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9222
0
                    authName, code, geodetic_crs_type);
9223
0
                for (const auto &candidate : l_candidates) {
9224
0
                    candidates.emplace_back(candidate);
9225
0
                }
9226
0
            }
9227
0
        }
9228
0
    } else if (datumName != "unknown" && datumName != "unnamed") {
9229
0
        auto matches = createObjectsFromName(
9230
0
            datumName,
9231
0
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
9232
0
            2);
9233
0
        if (matches.size() == 1) {
9234
0
            const auto &match = matches.front();
9235
0
            if (datum->_isEquivalentTo(match.get(),
9236
0
                                       util::IComparable::Criterion::EQUIVALENT,
9237
0
                                       databaseContext().as_nullable()) &&
9238
0
                !match->identifiers().empty()) {
9239
0
                return createGeodeticCRSFromDatum(
9240
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
9241
0
                        match),
9242
0
                    preferredAuthName, geodetic_crs_type);
9243
0
            }
9244
0
        }
9245
0
    }
9246
0
    return candidates;
9247
0
}
9248
//! @endcond
9249
9250
// ---------------------------------------------------------------------------
9251
9252
//! @cond Doxygen_Suppress
9253
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
9254
0
    const std::string &datum_auth_name, const std::string &datum_code) const {
9255
0
    std::string sql(
9256
0
        "SELECT auth_name, code FROM vertical_crs WHERE "
9257
0
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9258
0
    ListOfParams params{datum_auth_name, datum_code};
9259
0
    if (d->hasAuthorityRestriction()) {
9260
0
        sql += " AND auth_name = ?";
9261
0
        params.emplace_back(d->authority());
9262
0
    }
9263
0
    auto sqlRes = d->run(sql, params);
9264
0
    std::list<crs::VerticalCRSNNPtr> res;
9265
0
    for (const auto &row : sqlRes) {
9266
0
        const auto &auth_name = row[0];
9267
0
        const auto &code = row[1];
9268
0
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
9269
0
    }
9270
0
    return res;
9271
0
}
9272
//! @endcond
9273
9274
// ---------------------------------------------------------------------------
9275
9276
//! @cond Doxygen_Suppress
9277
std::list<crs::GeodeticCRSNNPtr>
9278
AuthorityFactory::createGeodeticCRSFromEllipsoid(
9279
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
9280
0
    const std::string &geodetic_crs_type) const {
9281
0
    std::string sql(
9282
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
9283
0
        "JOIN geodetic_datum ON "
9284
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
9285
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
9286
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
9287
0
        "geodetic_datum.ellipsoid_code = ? AND "
9288
0
        "geodetic_datum.deprecated = 0 AND "
9289
0
        "geodetic_crs.deprecated = 0");
9290
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
9291
0
    if (d->hasAuthorityRestriction()) {
9292
0
        sql += " AND geodetic_crs.auth_name = ?";
9293
0
        params.emplace_back(d->authority());
9294
0
    }
9295
0
    if (!geodetic_crs_type.empty()) {
9296
0
        sql += " AND geodetic_crs.type = ?";
9297
0
        params.emplace_back(geodetic_crs_type);
9298
0
    }
9299
0
    auto sqlRes = d->run(sql, params);
9300
0
    std::list<crs::GeodeticCRSNNPtr> res;
9301
0
    for (const auto &row : sqlRes) {
9302
0
        const auto &auth_name = row[0];
9303
0
        const auto &code = row[1];
9304
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9305
0
    }
9306
0
    return res;
9307
0
}
9308
//! @endcond
9309
9310
// ---------------------------------------------------------------------------
9311
9312
//! @cond Doxygen_Suppress
9313
static std::string buildSqlLookForAuthNameCode(
9314
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
9315
0
    const char *prefixField) {
9316
0
    std::string sql("(");
9317
9318
0
    std::set<std::string> authorities;
9319
0
    for (const auto &crs : list) {
9320
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9321
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9322
0
                                   : crs.first->identifiers();
9323
0
        if (!ids.empty()) {
9324
0
            authorities.insert(*(ids[0]->codeSpace()));
9325
0
        }
9326
0
    }
9327
0
    bool firstAuth = true;
9328
0
    for (const auto &auth_name : authorities) {
9329
0
        if (!firstAuth) {
9330
0
            sql += " OR ";
9331
0
        }
9332
0
        firstAuth = false;
9333
0
        sql += "( ";
9334
0
        sql += prefixField;
9335
0
        sql += "auth_name = ? AND ";
9336
0
        sql += prefixField;
9337
0
        sql += "code IN (";
9338
0
        params.emplace_back(auth_name);
9339
0
        bool firstGeodCRSForAuth = true;
9340
0
        for (const auto &crs : list) {
9341
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9342
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9343
0
                                       : crs.first->identifiers();
9344
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
9345
0
                if (!firstGeodCRSForAuth) {
9346
0
                    sql += ',';
9347
0
                }
9348
0
                firstGeodCRSForAuth = false;
9349
0
                sql += '?';
9350
0
                params.emplace_back(ids[0]->code());
9351
0
            }
9352
0
        }
9353
0
        sql += "))";
9354
0
    }
9355
0
    sql += ')';
9356
0
    return sql;
9357
0
}
9358
//! @endcond
9359
9360
// ---------------------------------------------------------------------------
9361
9362
//! @cond Doxygen_Suppress
9363
std::list<crs::ProjectedCRSNNPtr>
9364
AuthorityFactory::createProjectedCRSFromExisting(
9365
0
    const crs::ProjectedCRSNNPtr &crs) const {
9366
0
    std::list<crs::ProjectedCRSNNPtr> res;
9367
9368
0
    const auto &conv = crs->derivingConversionRef();
9369
0
    const auto &method = conv->method();
9370
0
    const auto methodEPSGCode = method->getEPSGCode();
9371
0
    if (methodEPSGCode == 0) {
9372
0
        return res;
9373
0
    }
9374
9375
0
    auto lockedThisFactory(d->getSharedFromThis());
9376
0
    assert(lockedThisFactory);
9377
0
    const auto &baseCRS(crs->baseCRS());
9378
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
9379
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
9380
0
    if (geogCRS) {
9381
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
9382
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
9383
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
9384
0
            const auto &unit =
9385
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
9386
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
9387
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
9388
0
                                        geogCRS->nameStr()),
9389
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
9390
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
9391
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
9392
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
9393
0
            auto otherCandidatesGeodCRS =
9394
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
9395
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
9396
0
                                     otherCandidatesGeodCRS.begin(),
9397
0
                                     otherCandidatesGeodCRS.end());
9398
0
        }
9399
0
    }
9400
9401
0
    std::string sql(
9402
0
        "SELECT projected_crs.auth_name, projected_crs.code FROM projected_crs "
9403
0
        "JOIN conversion_table conv ON "
9404
0
        "projected_crs.conversion_auth_name = conv.auth_name AND "
9405
0
        "projected_crs.conversion_code = conv.code WHERE "
9406
0
        "projected_crs.deprecated = 0 AND ");
9407
0
    ListOfParams params;
9408
0
    if (!candidatesGeodCRS.empty()) {
9409
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
9410
0
                                           "projected_crs.geodetic_crs_");
9411
0
        sql += " AND ";
9412
0
    }
9413
0
    sql += "conv.method_auth_name = 'EPSG' AND "
9414
0
           "conv.method_code = ?";
9415
0
    params.emplace_back(toString(methodEPSGCode));
9416
0
    if (d->hasAuthorityRestriction()) {
9417
0
        sql += " AND projected_crs.auth_name = ?";
9418
0
        params.emplace_back(d->authority());
9419
0
    }
9420
9421
0
    int iParam = 0;
9422
0
    bool hasLat1stStd = false;
9423
0
    double lat1stStd = 0;
9424
0
    int iParamLat1stStd = 0;
9425
0
    bool hasLat2ndStd = false;
9426
0
    double lat2ndStd = 0;
9427
0
    int iParamLat2ndStd = 0;
9428
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
9429
0
        iParam++;
9430
0
        auto opParamvalue =
9431
0
            dynamic_cast<const operation::OperationParameterValue *>(
9432
0
                genOpParamvalue.get());
9433
0
        if (!opParamvalue) {
9434
0
            break;
9435
0
        }
9436
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
9437
0
        const auto &parameterValue = opParamvalue->parameterValue();
9438
0
        if (!(paramEPSGCode > 0 &&
9439
0
              parameterValue->type() ==
9440
0
                  operation::ParameterValue::Type::MEASURE)) {
9441
0
            break;
9442
0
        }
9443
0
        const auto &measure = parameterValue->value();
9444
0
        const auto &unit = measure.unit();
9445
0
        if (unit == common::UnitOfMeasure::DEGREE &&
9446
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
9447
0
            if (methodEPSGCode ==
9448
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
9449
                // Special case for standard parallels of LCC_2SP. See below
9450
0
                if (paramEPSGCode ==
9451
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
9452
0
                    hasLat1stStd = true;
9453
0
                    lat1stStd = measure.value();
9454
0
                    iParamLat1stStd = iParam;
9455
0
                    continue;
9456
0
                } else if (paramEPSGCode ==
9457
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
9458
0
                    hasLat2ndStd = true;
9459
0
                    lat2ndStd = measure.value();
9460
0
                    iParamLat2ndStd = iParam;
9461
0
                    continue;
9462
0
                }
9463
0
            }
9464
0
            const auto iParamAsStr(toString(iParam));
9465
0
            sql += " AND conv.param";
9466
0
            sql += iParamAsStr;
9467
0
            sql += "_code = ? AND conv.param";
9468
0
            sql += iParamAsStr;
9469
0
            sql += "_auth_name = 'EPSG' AND conv.param";
9470
0
            sql += iParamAsStr;
9471
0
            sql += "_value BETWEEN ? AND ?";
9472
            // As angles might be expressed with the odd unit EPSG:9110
9473
            // "sexagesimal DMS", we have to provide a broad range
9474
0
            params.emplace_back(toString(paramEPSGCode));
9475
0
            params.emplace_back(measure.value() - 1);
9476
0
            params.emplace_back(measure.value() + 1);
9477
0
        }
9478
0
    }
9479
9480
    // Special case for standard parallels of LCC_2SP: they can be switched
9481
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
9482
0
        hasLat1stStd && hasLat2ndStd) {
9483
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
9484
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
9485
0
        sql += " AND conv.param";
9486
0
        sql += iParam1AsStr;
9487
0
        sql += "_code = ? AND conv.param";
9488
0
        sql += iParam1AsStr;
9489
0
        sql += "_auth_name = 'EPSG' AND conv.param";
9490
0
        sql += iParam2AsStr;
9491
0
        sql += "_code = ? AND conv.param";
9492
0
        sql += iParam2AsStr;
9493
0
        sql += "_auth_name = 'EPSG' AND ((";
9494
0
        params.emplace_back(
9495
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
9496
0
        params.emplace_back(
9497
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
9498
0
        double val1 = lat1stStd;
9499
0
        double val2 = lat2ndStd;
9500
0
        for (int i = 0; i < 2; i++) {
9501
0
            if (i == 1) {
9502
0
                sql += ") OR (";
9503
0
                std::swap(val1, val2);
9504
0
            }
9505
0
            sql += "conv.param";
9506
0
            sql += iParam1AsStr;
9507
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
9508
0
            sql += iParam2AsStr;
9509
0
            sql += "_value BETWEEN ? AND ?";
9510
0
            params.emplace_back(val1 - 1);
9511
0
            params.emplace_back(val1 + 1);
9512
0
            params.emplace_back(val2 - 1);
9513
0
            params.emplace_back(val2 + 1);
9514
0
        }
9515
0
        sql += "))";
9516
0
    }
9517
0
    auto sqlRes = d->run(sql, params);
9518
9519
0
    params.clear();
9520
9521
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
9522
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
9523
0
    if (!candidatesGeodCRS.empty()) {
9524
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
9525
0
                                           "geodetic_crs_");
9526
0
        sql += " AND ";
9527
0
    }
9528
9529
0
    const auto escapeLikeStr = [](const std::string &str) {
9530
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
9531
0
                          "%", "\\%");
9532
0
    };
9533
9534
0
    const auto ellpsSemiMajorStr =
9535
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
9536
9537
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
9538
9539
    // WKT2 definition
9540
0
    {
9541
0
        std::string patternVal("%");
9542
9543
0
        patternVal += ',';
9544
0
        patternVal += ellpsSemiMajorStr;
9545
0
        patternVal += '%';
9546
9547
0
        patternVal += escapeLikeStr(method->nameStr());
9548
0
        patternVal += '%';
9549
9550
0
        params.emplace_back(patternVal);
9551
0
    }
9552
9553
0
    const auto *mapping = getMapping(method.get());
9554
0
    if (mapping && mapping->proj_name_main) {
9555
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
9556
9557
0
        std::string patternVal("%");
9558
0
        patternVal += "proj=";
9559
0
        patternVal += mapping->proj_name_main;
9560
0
        patternVal += '%';
9561
0
        params.emplace_back(patternVal);
9562
9563
        // could be a= or R=
9564
0
        patternVal = "%=";
9565
0
        patternVal += ellpsSemiMajorStr;
9566
0
        patternVal += '%';
9567
0
        params.emplace_back(patternVal);
9568
9569
0
        std::string projEllpsName;
9570
0
        std::string ellpsName;
9571
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
9572
0
                                                            ellpsName)) {
9573
0
            sql += " OR text_definition LIKE ?";
9574
            // Could be ellps= or datum=
9575
0
            patternVal = "%=";
9576
0
            patternVal += projEllpsName;
9577
0
            patternVal += '%';
9578
0
            params.emplace_back(patternVal);
9579
0
        }
9580
9581
0
        sql += "))";
9582
0
    }
9583
9584
    // WKT1_GDAL definition
9585
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
9586
0
    if (wkt1GDALMethodName) {
9587
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
9588
0
        std::string patternVal("%");
9589
9590
0
        patternVal += ',';
9591
0
        patternVal += ellpsSemiMajorStr;
9592
0
        patternVal += '%';
9593
9594
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
9595
0
        patternVal += '%';
9596
9597
0
        params.emplace_back(patternVal);
9598
0
    }
9599
9600
    // WKT1_ESRI definition
9601
0
    const char *esriMethodName = conv->getESRIMethodName();
9602
0
    if (esriMethodName) {
9603
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
9604
0
        std::string patternVal("%");
9605
9606
0
        patternVal += ',';
9607
0
        patternVal += ellpsSemiMajorStr;
9608
0
        patternVal += '%';
9609
9610
0
        patternVal += escapeLikeStr(esriMethodName);
9611
0
        patternVal += '%';
9612
9613
0
        auto fe =
9614
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
9615
0
        if (*fe == Measure()) {
9616
0
            fe = &conv->parameterValueMeasure(
9617
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
9618
0
        }
9619
0
        if (!(*fe == Measure())) {
9620
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
9621
0
            patternVal +=
9622
0
                toString(fe->convertToUnit(
9623
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
9624
0
                         10);
9625
0
            patternVal += '%';
9626
0
        }
9627
9628
0
        auto lat = &conv->parameterValueMeasure(
9629
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
9630
0
        if (*lat == Measure()) {
9631
0
            lat = &conv->parameterValueMeasure(
9632
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
9633
0
        }
9634
0
        if (!(*lat == Measure())) {
9635
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
9636
0
            const auto &angularUnit =
9637
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
9638
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
9639
0
                    : UnitOfMeasure::DEGREE;
9640
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
9641
0
            patternVal += '%';
9642
0
        }
9643
9644
0
        params.emplace_back(patternVal);
9645
0
    }
9646
0
    sql += ")";
9647
0
    if (d->hasAuthorityRestriction()) {
9648
0
        sql += " AND auth_name = ?";
9649
0
        params.emplace_back(d->authority());
9650
0
    }
9651
9652
0
    auto sqlRes2 = d->run(sql, params);
9653
9654
0
    if (sqlRes.size() <= 200) {
9655
0
        for (const auto &row : sqlRes) {
9656
0
            const auto &auth_name = row[0];
9657
0
            const auto &code = row[1];
9658
0
            res.emplace_back(
9659
0
                d->createFactory(auth_name)->createProjectedCRS(code));
9660
0
        }
9661
0
    }
9662
0
    if (sqlRes2.size() <= 200) {
9663
0
        for (const auto &row : sqlRes2) {
9664
0
            const auto &auth_name = row[0];
9665
0
            const auto &code = row[1];
9666
0
            res.emplace_back(
9667
0
                d->createFactory(auth_name)->createProjectedCRS(code));
9668
0
        }
9669
0
    }
9670
9671
0
    return res;
9672
0
}
9673
9674
// ---------------------------------------------------------------------------
9675
9676
std::list<crs::CompoundCRSNNPtr>
9677
AuthorityFactory::createCompoundCRSFromExisting(
9678
0
    const crs::CompoundCRSNNPtr &crs) const {
9679
0
    std::list<crs::CompoundCRSNNPtr> res;
9680
9681
0
    auto lockedThisFactory(d->getSharedFromThis());
9682
0
    assert(lockedThisFactory);
9683
9684
0
    const auto &components = crs->componentReferenceSystems();
9685
0
    if (components.size() != 2) {
9686
0
        return res;
9687
0
    }
9688
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
9689
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
9690
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
9691
0
        return res;
9692
0
    }
9693
9694
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
9695
0
                    "deprecated = 0 AND ");
9696
0
    ListOfParams params;
9697
0
    bool addAnd = false;
9698
0
    if (!candidatesHorizCRS.empty()) {
9699
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
9700
0
                                           "horiz_crs_");
9701
0
        addAnd = true;
9702
0
    }
9703
0
    if (!candidatesVertCRS.empty()) {
9704
0
        if (addAnd) {
9705
0
            sql += " AND ";
9706
0
        }
9707
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
9708
0
                                           "vertical_crs_");
9709
0
        addAnd = true;
9710
0
    }
9711
0
    if (d->hasAuthorityRestriction()) {
9712
0
        if (addAnd) {
9713
0
            sql += " AND ";
9714
0
        }
9715
0
        sql += "auth_name = ?";
9716
0
        params.emplace_back(d->authority());
9717
0
    }
9718
9719
0
    auto sqlRes = d->run(sql, params);
9720
0
    for (const auto &row : sqlRes) {
9721
0
        const auto &auth_name = row[0];
9722
0
        const auto &code = row[1];
9723
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
9724
0
    }
9725
0
    return res;
9726
0
}
9727
9728
// ---------------------------------------------------------------------------
9729
9730
std::vector<operation::CoordinateOperationNNPtr>
9731
AuthorityFactory::getTransformationsForGeoid(
9732
0
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
9733
0
    std::vector<operation::CoordinateOperationNNPtr> res;
9734
9735
0
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
9736
0
                          "geoid_model WHERE name = ?");
9737
0
    auto sqlRes = d->run(sql, {geoidName});
9738
0
    for (const auto &row : sqlRes) {
9739
0
        const auto &auth_name = row[0];
9740
0
        const auto &code = row[1];
9741
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
9742
0
            code, usePROJAlternativeGridNames));
9743
0
    }
9744
9745
0
    return res;
9746
0
}
9747
9748
// ---------------------------------------------------------------------------
9749
9750
std::vector<operation::PointMotionOperationNNPtr>
9751
AuthorityFactory::getPointMotionOperationsFor(
9752
0
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
9753
0
    std::vector<operation::PointMotionOperationNNPtr> res;
9754
0
    const auto crsList =
9755
0
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
9756
0
                                   /* preferredAuthName = */ std::string(),
9757
0
                                   /* geodetic_crs_type = */ std::string());
9758
0
    if (crsList.empty())
9759
0
        return res;
9760
0
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
9761
0
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
9762
0
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
9763
0
                    "(");
9764
0
    bool addOr = false;
9765
0
    ListOfParams params;
9766
0
    for (const auto &candidateCrs : crsList) {
9767
0
        if (addOr)
9768
0
            sql += " OR ";
9769
0
        addOr = true;
9770
0
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
9771
0
        const auto &ids = candidateCrs->identifiers();
9772
0
        params.emplace_back(*(ids[0]->codeSpace()));
9773
0
        params.emplace_back(ids[0]->code());
9774
0
    }
9775
0
    sql += ")";
9776
0
    if (d->hasAuthorityRestriction()) {
9777
0
        sql += " AND auth_name = ?";
9778
0
        params.emplace_back(d->authority());
9779
0
    }
9780
9781
0
    auto sqlRes = d->run(sql, params);
9782
0
    for (const auto &row : sqlRes) {
9783
0
        const auto &auth_name = row[0];
9784
0
        const auto &code = row[1];
9785
0
        auto pmo =
9786
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
9787
0
                d->createFactory(auth_name)->createCoordinateOperation(
9788
0
                    code, usePROJAlternativeGridNames));
9789
0
        if (pmo) {
9790
0
            res.emplace_back(NN_NO_CHECK(pmo));
9791
0
        }
9792
0
    }
9793
0
    return res;
9794
0
}
9795
9796
//! @endcond
9797
9798
// ---------------------------------------------------------------------------
9799
9800
//! @cond Doxygen_Suppress
9801
70.6k
FactoryException::FactoryException(const char *message) : Exception(message) {}
9802
9803
// ---------------------------------------------------------------------------
9804
9805
FactoryException::FactoryException(const std::string &message)
9806
0
    : Exception(message) {}
9807
9808
// ---------------------------------------------------------------------------
9809
9810
70.6k
FactoryException::~FactoryException() = default;
9811
9812
// ---------------------------------------------------------------------------
9813
9814
0
FactoryException::FactoryException(const FactoryException &) = default;
9815
//! @endcond
9816
9817
// ---------------------------------------------------------------------------
9818
9819
//! @cond Doxygen_Suppress
9820
9821
struct NoSuchAuthorityCodeException::Private {
9822
    std::string authority_;
9823
    std::string code_;
9824
9825
    Private(const std::string &authority, const std::string &code)
9826
0
        : authority_(authority), code_(code) {}
9827
};
9828
9829
// ---------------------------------------------------------------------------
9830
9831
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
9832
    const std::string &message, const std::string &authority,
9833
    const std::string &code)
9834
    : FactoryException(message),
9835
0
      d(internal::make_unique<Private>(authority, code)) {}
9836
9837
// ---------------------------------------------------------------------------
9838
9839
0
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
9840
9841
// ---------------------------------------------------------------------------
9842
9843
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
9844
    const NoSuchAuthorityCodeException &other)
9845
0
    : FactoryException(other), d(internal::make_unique<Private>(*(other.d))) {}
9846
//! @endcond
9847
9848
// ---------------------------------------------------------------------------
9849
9850
/** \brief Returns authority name. */
9851
0
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
9852
0
    return d->authority_;
9853
0
}
9854
9855
// ---------------------------------------------------------------------------
9856
9857
/** \brief Returns authority code. */
9858
0
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
9859
0
    return d->code_;
9860
0
}
9861
9862
// ---------------------------------------------------------------------------
9863
9864
} // namespace io
9865
NS_PROJ_END
9866
9867
// ---------------------------------------------------------------------------
9868
9869
28.9k
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }