Coverage Report

Created: 2025-06-13 06:29

/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
#ifdef EMBED_RESOURCE_FILES
80
#include "embedded_resources.h"
81
#endif
82
83
// Custom SQLite VFS as our database is not supposed to be modified in
84
// parallel. This is slightly faster
85
#define ENABLE_CUSTOM_LOCKLESS_VFS
86
87
#if defined(_WIN32) && defined(PROJ_HAS_PTHREADS)
88
#undef PROJ_HAS_PTHREADS
89
#endif
90
91
/* SQLite3 might use seak()+read() or pread[64]() to read data */
92
/* The later allows the same SQLite handle to be safely used in forked */
93
/* children of a parent process, while the former doesn't. */
94
/* So we use pthread_atfork() to set a flag in forked children, to ask them */
95
/* to close and reopen their database handle. */
96
#if defined(PROJ_HAS_PTHREADS) && !defined(SQLITE_USE_PREAD)
97
#include <pthread.h>
98
#define REOPEN_SQLITE_DB_AFTER_FORK
99
#endif
100
101
using namespace NS_PROJ::internal;
102
using namespace NS_PROJ::common;
103
104
NS_PROJ_START
105
namespace io {
106
107
//! @cond Doxygen_Suppress
108
109
// CRS subtypes
110
8.13k
#define GEOG_2D "geographic 2D"
111
2.93k
#define GEOG_3D "geographic 3D"
112
2.93k
#define GEOCENTRIC "geocentric"
113
400
#define OTHER "other"
114
310
#define PROJECTED "projected"
115
141
#define ENGINEERING "engineering"
116
384
#define VERTICAL "vertical"
117
140
#define COMPOUND "compound"
118
119
0
#define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
120
0
#define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
121
#define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
122
123
// Coordinate system types
124
constexpr const char *CS_TYPE_ELLIPSOIDAL = cs::EllipsoidalCS::WKT2_TYPE;
125
constexpr const char *CS_TYPE_CARTESIAN = cs::CartesianCS::WKT2_TYPE;
126
constexpr const char *CS_TYPE_SPHERICAL = cs::SphericalCS::WKT2_TYPE;
127
constexpr const char *CS_TYPE_VERTICAL = cs::VerticalCS::WKT2_TYPE;
128
constexpr const char *CS_TYPE_ORDINAL = cs::OrdinalCS::WKT2_TYPE;
129
130
// See data/sql/metadata.sql for the semantics of those constants
131
constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
132
// If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
133
// must be incremented.
134
constexpr int DATABASE_LAYOUT_VERSION_MINOR = 5;
135
136
constexpr size_t N_MAX_PARAMS = 7;
137
138
#ifdef EMBED_RESOURCE_FILES
139
constexpr const char *EMBEDDED_PROJ_DB = "__embedded_proj_db__";
140
#endif
141
142
// ---------------------------------------------------------------------------
143
144
struct SQLValues {
145
    enum class Type { STRING, INT, DOUBLE };
146
147
    // cppcheck-suppress noExplicitConstructor
148
191k
    SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
149
150
    // cppcheck-suppress noExplicitConstructor
151
0
    SQLValues(int value) : type_(Type::INT), int_(value) {}
152
153
    // cppcheck-suppress noExplicitConstructor
154
20.8k
    SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
155
156
212k
    const Type &type() const { return type_; }
157
158
    // cppcheck-suppress functionStatic
159
191k
    const std::string &stringValue() const { return str_; }
160
161
    // cppcheck-suppress functionStatic
162
0
    int intValue() const { return int_; }
163
164
    // cppcheck-suppress functionStatic
165
20.8k
    double doubleValue() const { return double_; }
166
167
  private:
168
    Type type_;
169
    std::string str_{};
170
    int int_ = 0;
171
    double double_ = 0.0;
172
};
173
174
// ---------------------------------------------------------------------------
175
176
using SQLRow = std::vector<std::string>;
177
using SQLResultSet = std::list<SQLRow>;
178
using ListOfParams = std::list<SQLValues>;
179
180
// ---------------------------------------------------------------------------
181
182
0
static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
183
0
    switch (sqlite3_value_type(val)) {
184
0
    case SQLITE_FLOAT:
185
0
        gotVal = true;
186
0
        return sqlite3_value_double(val);
187
188
0
    case SQLITE_INTEGER:
189
0
        gotVal = true;
190
0
        return static_cast<double>(sqlite3_value_int64(val));
191
192
0
    default:
193
0
        gotVal = false;
194
0
        return 0.0;
195
0
    }
196
0
}
197
198
// ---------------------------------------------------------------------------
199
200
static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
201
                                              int /* argc */,
202
0
                                              sqlite3_value **argv) {
203
0
    bool b0, b1, b2, b3;
204
0
    double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
205
0
    double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
206
0
    double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
207
0
    double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
208
0
    if (!b0 || !b1 || !b2 || !b3) {
209
0
        sqlite3_result_null(pContext);
210
0
        return;
211
0
    }
212
    // Deal with area crossing antimeridian
213
0
    if (east_lon < west_lon) {
214
0
        east_lon += 360.0;
215
0
    }
216
    // Integrate cos(lat) between south_lat and north_lat
217
0
    double pseudo_area = (east_lon - west_lon) *
218
0
                         (std::sin(common::Angle(north_lat).getSIValue()) -
219
0
                          std::sin(common::Angle(south_lat).getSIValue()));
220
0
    sqlite3_result_double(pContext, pseudo_area);
221
0
}
222
223
// ---------------------------------------------------------------------------
224
225
static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
226
0
                                        int /* argc */, sqlite3_value **argv) {
227
0
    bool b0, b1, b2, b3, b4, b5, b6, b7;
228
0
    double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
229
0
    double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
230
0
    double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
231
0
    double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
232
0
    double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
233
0
    double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
234
0
    double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
235
0
    double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
236
0
    if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
237
0
        sqlite3_result_null(pContext);
238
0
        return;
239
0
    }
240
0
    auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
241
0
                                                         east_lon1, north_lat1);
242
0
    auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
243
0
                                                         east_lon2, north_lat2);
244
0
    sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
245
0
}
246
247
// ---------------------------------------------------------------------------
248
249
class SQLiteHandle {
250
    std::string path_{};
251
    sqlite3 *sqlite_handle_ = nullptr;
252
    bool close_handle_ = true;
253
254
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
255
    bool is_valid_ = true;
256
#endif
257
258
    int nLayoutVersionMajor_ = 0;
259
    int nLayoutVersionMinor_ = 0;
260
261
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
262
    std::unique_ptr<SQLite3VFS> vfs_{};
263
#endif
264
265
    SQLiteHandle(const SQLiteHandle &) = delete;
266
    SQLiteHandle &operator=(const SQLiteHandle &) = delete;
267
268
    SQLiteHandle(sqlite3 *sqlite_handle, bool close_handle)
269
1
        : sqlite_handle_(sqlite_handle), close_handle_(close_handle) {
270
1
        assert(sqlite_handle_);
271
1
    }
272
273
    // cppcheck-suppress functionStatic
274
    void initialize();
275
276
    SQLResultSet run(const std::string &sql,
277
                     const ListOfParams &parameters = ListOfParams(),
278
                     bool useMaxFloatPrecision = false);
279
280
  public:
281
    ~SQLiteHandle();
282
283
824
    const std::string &path() const { return path_; }
284
285
1.20k
    sqlite3 *handle() { return sqlite_handle_; }
286
287
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
288
101k
    bool isValid() const { return is_valid_; }
289
290
0
    void invalidate() { is_valid_ = false; }
291
#endif
292
293
    static std::shared_ptr<SQLiteHandle> open(PJ_CONTEXT *ctx,
294
                                              const std::string &path);
295
296
    // might not be shared between thread depending how the handle was opened!
297
    static std::shared_ptr<SQLiteHandle>
298
    initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
299
                     int nLayoutVersionMajor, int nLayoutVersionMinor);
300
301
    static std::unique_ptr<SQLiteHandle>
302
    initFromExistingUniquePtr(sqlite3 *sqlite_handle, bool close_handle);
303
304
    void checkDatabaseLayout(const std::string &mainDbPath,
305
                             const std::string &path,
306
                             const std::string &dbNamePrefix);
307
308
    SQLResultSet run(sqlite3_stmt *stmt, const std::string &sql,
309
                     const ListOfParams &parameters = ListOfParams(),
310
                     bool useMaxFloatPrecision = false);
311
312
0
    inline int getLayoutVersionMajor() const { return nLayoutVersionMajor_; }
313
0
    inline int getLayoutVersionMinor() const { return nLayoutVersionMinor_; }
314
};
315
316
// ---------------------------------------------------------------------------
317
318
0
SQLiteHandle::~SQLiteHandle() {
319
0
    if (close_handle_) {
320
0
        sqlite3_close(sqlite_handle_);
321
0
    }
322
0
}
323
324
// ---------------------------------------------------------------------------
325
326
std::shared_ptr<SQLiteHandle> SQLiteHandle::open(PJ_CONTEXT *ctx,
327
1
                                                 const std::string &pathIn) {
328
329
1
    std::string path(pathIn);
330
1
    const int sqlite3VersionNumber = sqlite3_libversion_number();
331
    // Minimum version for correct performance: 3.11
332
1
    if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
333
0
        pj_log(ctx, PJ_LOG_ERROR,
334
0
               "SQLite3 version is %s, whereas at least 3.11 should be used",
335
0
               sqlite3_libversion());
336
0
    }
337
338
1
    std::string vfsName;
339
1
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
340
1
    std::unique_ptr<SQLite3VFS> vfs;
341
1
#endif
342
343
1
#ifdef EMBED_RESOURCE_FILES
344
1
    if (path == EMBEDDED_PROJ_DB && ctx->custom_sqlite3_vfs_name.empty()) {
345
1
        unsigned int proj_db_size = 0;
346
1
        const unsigned char *proj_db = pj_get_embedded_proj_db(&proj_db_size);
347
348
1
        vfs = SQLite3VFS::createMem(proj_db, proj_db_size);
349
1
        if (vfs == nullptr) {
350
0
            throw FactoryException("Open of " + path + " failed");
351
0
        }
352
353
1
        std::ostringstream buffer;
354
1
        buffer << "file:/proj.db?immutable=1&ptr=";
355
1
        buffer << reinterpret_cast<uintptr_t>(proj_db);
356
1
        buffer << "&sz=";
357
1
        buffer << proj_db_size;
358
1
        buffer << "&max=";
359
1
        buffer << proj_db_size;
360
1
        buffer << "&vfs=";
361
1
        buffer << vfs->name();
362
1
        path = buffer.str();
363
1
    } else
364
0
#endif
365
366
0
#ifdef ENABLE_CUSTOM_LOCKLESS_VFS
367
0
        if (ctx->custom_sqlite3_vfs_name.empty()) {
368
0
        vfs = SQLite3VFS::create(false, true, true);
369
0
        if (vfs == nullptr) {
370
0
            throw FactoryException("Open of " + path + " failed");
371
0
        }
372
0
        vfsName = vfs->name();
373
0
    } else
374
0
#endif
375
0
    {
376
0
        vfsName = ctx->custom_sqlite3_vfs_name;
377
0
    }
378
1
    sqlite3 *sqlite_handle = nullptr;
379
    // SQLITE_OPEN_FULLMUTEX as this will be used from concurrent threads
380
1
    if (sqlite3_open_v2(
381
1
            path.c_str(), &sqlite_handle,
382
1
            SQLITE_OPEN_READONLY | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_URI,
383
1
            vfsName.empty() ? nullptr : vfsName.c_str()) != SQLITE_OK ||
384
1
        !sqlite_handle) {
385
0
        if (sqlite_handle != nullptr) {
386
0
            sqlite3_close(sqlite_handle);
387
0
        }
388
0
        throw FactoryException("Open of " + path + " failed");
389
0
    }
390
1
    auto handle =
391
1
        std::shared_ptr<SQLiteHandle>(new SQLiteHandle(sqlite_handle, true));
392
1
#if defined(ENABLE_CUSTOM_LOCKLESS_VFS) || defined(EMBED_RESOURCE_FILES)
393
1
    handle->vfs_ = std::move(vfs);
394
1
#endif
395
1
    handle->initialize();
396
1
    handle->path_ = path;
397
1
    handle->checkDatabaseLayout(path, path, std::string());
398
1
    return handle;
399
1
}
400
401
// ---------------------------------------------------------------------------
402
403
std::shared_ptr<SQLiteHandle>
404
SQLiteHandle::initFromExisting(sqlite3 *sqlite_handle, bool close_handle,
405
                               int nLayoutVersionMajor,
406
0
                               int nLayoutVersionMinor) {
407
0
    auto handle = std::shared_ptr<SQLiteHandle>(
408
0
        new SQLiteHandle(sqlite_handle, close_handle));
409
0
    handle->nLayoutVersionMajor_ = nLayoutVersionMajor;
410
0
    handle->nLayoutVersionMinor_ = nLayoutVersionMinor;
411
0
    handle->initialize();
412
0
    return handle;
413
0
}
414
415
// ---------------------------------------------------------------------------
416
417
std::unique_ptr<SQLiteHandle>
418
SQLiteHandle::initFromExistingUniquePtr(sqlite3 *sqlite_handle,
419
0
                                        bool close_handle) {
420
0
    auto handle = std::unique_ptr<SQLiteHandle>(
421
0
        new SQLiteHandle(sqlite_handle, close_handle));
422
0
    handle->initialize();
423
0
    return handle;
424
0
}
425
426
// ---------------------------------------------------------------------------
427
428
SQLResultSet SQLiteHandle::run(sqlite3_stmt *stmt, const std::string &sql,
429
                               const ListOfParams &parameters,
430
101k
                               bool useMaxFloatPrecision) {
431
101k
    int nBindField = 1;
432
212k
    for (const auto &param : parameters) {
433
212k
        const auto &paramType = param.type();
434
212k
        if (paramType == SQLValues::Type::STRING) {
435
191k
            const auto &strValue = param.stringValue();
436
191k
            sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
437
191k
                              static_cast<int>(strValue.size()),
438
191k
                              SQLITE_TRANSIENT);
439
191k
        } else if (paramType == SQLValues::Type::INT) {
440
0
            sqlite3_bind_int(stmt, nBindField, param.intValue());
441
20.8k
        } else {
442
20.8k
            assert(paramType == SQLValues::Type::DOUBLE);
443
20.8k
            sqlite3_bind_double(stmt, nBindField, param.doubleValue());
444
20.8k
        }
445
212k
        nBindField++;
446
212k
    }
447
448
#ifdef TRACE_DATABASE
449
    size_t nPos = 0;
450
    std::string sqlSubst(sql);
451
    for (const auto &param : parameters) {
452
        nPos = sqlSubst.find('?', nPos);
453
        assert(nPos != std::string::npos);
454
        std::string strValue;
455
        const auto paramType = param.type();
456
        if (paramType == SQLValues::Type::STRING) {
457
            strValue = '\'' + param.stringValue() + '\'';
458
        } else if (paramType == SQLValues::Type::INT) {
459
            strValue = toString(param.intValue());
460
        } else {
461
            strValue = toString(param.doubleValue());
462
        }
463
        sqlSubst =
464
            sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
465
        nPos += strValue.size();
466
    }
467
    logTrace(sqlSubst, "DATABASE");
468
#endif
469
470
101k
    SQLResultSet result;
471
101k
    const int column_count = sqlite3_column_count(stmt);
472
81.2M
    while (true) {
473
81.2M
        int ret = sqlite3_step(stmt);
474
81.2M
        if (ret == SQLITE_ROW) {
475
81.0M
            SQLRow row(column_count);
476
567M
            for (int i = 0; i < column_count; i++) {
477
486M
                if (useMaxFloatPrecision &&
478
486M
                    sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
479
                    // sqlite3_column_text() does not use maximum precision
480
81
                    std::ostringstream buffer;
481
81
                    buffer.imbue(std::locale::classic());
482
81
                    buffer << std::setprecision(18);
483
81
                    buffer << sqlite3_column_double(stmt, i);
484
81
                    row[i] = buffer.str();
485
486M
                } else {
486
486M
                    const char *txt = reinterpret_cast<const char *>(
487
486M
                        sqlite3_column_text(stmt, i));
488
486M
                    if (txt) {
489
486M
                        row[i] = txt;
490
486M
                    }
491
486M
                }
492
486M
            }
493
81.0M
            result.emplace_back(std::move(row));
494
81.0M
        } else if (ret == SQLITE_DONE) {
495
101k
            break;
496
101k
        } else {
497
0
            throw FactoryException(std::string("SQLite error on ")
498
0
                                       .append(sql)
499
0
                                       .append(": code = ")
500
0
                                       .append(internal::toString(ret))
501
0
                                       .append(", msg = ")
502
0
                                       .append(sqlite3_errmsg(sqlite_handle_)));
503
0
        }
504
81.2M
    }
505
101k
    return result;
506
101k
}
507
508
// ---------------------------------------------------------------------------
509
510
SQLResultSet SQLiteHandle::run(const std::string &sql,
511
                               const ListOfParams &parameters,
512
1
                               bool useMaxFloatPrecision) {
513
1
    sqlite3_stmt *stmt = nullptr;
514
1
    try {
515
1
        if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
516
1
                               static_cast<int>(sql.size()), &stmt,
517
1
                               nullptr) != SQLITE_OK) {
518
0
            throw FactoryException("SQLite error on " + sql + ": " +
519
0
                                   sqlite3_errmsg(sqlite_handle_));
520
0
        }
521
1
        auto ret = run(stmt, sql, parameters, useMaxFloatPrecision);
522
1
        sqlite3_finalize(stmt);
523
1
        return ret;
524
1
    } catch (const std::exception &) {
525
0
        if (stmt)
526
0
            sqlite3_finalize(stmt);
527
0
        throw;
528
0
    }
529
1
}
530
531
// ---------------------------------------------------------------------------
532
533
void SQLiteHandle::checkDatabaseLayout(const std::string &mainDbPath,
534
                                       const std::string &path,
535
1
                                       const std::string &dbNamePrefix) {
536
1
    if (!dbNamePrefix.empty() && run("SELECT 1 FROM " + dbNamePrefix +
537
0
                                     "sqlite_master WHERE name = 'metadata'")
538
0
                                     .empty()) {
539
        // Accept auxiliary databases without metadata table (sparse DBs)
540
0
        return;
541
0
    }
542
1
    auto res = run("SELECT key, value FROM " + dbNamePrefix +
543
1
                   "metadata WHERE key IN "
544
1
                   "('DATABASE.LAYOUT.VERSION.MAJOR', "
545
1
                   "'DATABASE.LAYOUT.VERSION.MINOR')");
546
1
    if (res.empty() && !dbNamePrefix.empty()) {
547
        // Accept auxiliary databases without layout metadata.
548
0
        return;
549
0
    }
550
1
    if (res.size() != 2) {
551
0
        throw FactoryException(
552
0
            path + " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
553
0
                   "DATABASE.LAYOUT.VERSION.MINOR "
554
0
                   "metadata. It comes from another PROJ installation.");
555
0
    }
556
1
    int major = 0;
557
1
    int minor = 0;
558
2
    for (const auto &row : res) {
559
2
        if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
560
1
            major = atoi(row[1].c_str());
561
1
        } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
562
1
            minor = atoi(row[1].c_str());
563
1
        }
564
2
    }
565
1
    if (major != DATABASE_LAYOUT_VERSION_MAJOR) {
566
0
        throw FactoryException(
567
0
            path +
568
0
            " contains DATABASE.LAYOUT.VERSION.MAJOR = " + toString(major) +
569
0
            " whereas " + toString(DATABASE_LAYOUT_VERSION_MAJOR) +
570
0
            " is expected. "
571
0
            "It comes from another PROJ installation.");
572
0
    }
573
574
1
    if (minor < DATABASE_LAYOUT_VERSION_MINOR) {
575
0
        throw FactoryException(
576
0
            path +
577
0
            " contains DATABASE.LAYOUT.VERSION.MINOR = " + toString(minor) +
578
0
            " whereas a number >= " + toString(DATABASE_LAYOUT_VERSION_MINOR) +
579
0
            " is expected. "
580
0
            "It comes from another PROJ installation.");
581
0
    }
582
583
1
    if (dbNamePrefix.empty()) {
584
1
        nLayoutVersionMajor_ = major;
585
1
        nLayoutVersionMinor_ = minor;
586
1
    } else if (nLayoutVersionMajor_ != major || nLayoutVersionMinor_ != minor) {
587
0
        throw FactoryException(
588
0
            "Auxiliary database " + path +
589
0
            " contains a DATABASE.LAYOUT.VERSION =  " + toString(major) + '.' +
590
0
            toString(minor) +
591
0
            " which is different from the one from the main database " +
592
0
            mainDbPath + " which is " + toString(nLayoutVersionMajor_) + '.' +
593
0
            toString(nLayoutVersionMinor_));
594
0
    }
595
1
}
596
597
// ---------------------------------------------------------------------------
598
599
#ifndef SQLITE_DETERMINISTIC
600
#define SQLITE_DETERMINISTIC 0
601
#endif
602
603
1
void SQLiteHandle::initialize() {
604
605
    // There is a bug in sqlite 3.38.0 with some complex queries.
606
    // Cf https://github.com/OSGeo/PROJ/issues/3077
607
    // Disabling Bloom-filter pull-down optimization as suggested in
608
    // https://sqlite.org/forum/forumpost/7d3a75438c
609
1
    const int sqlite3VersionNumber = sqlite3_libversion_number();
610
1
    if (sqlite3VersionNumber == 3 * 1000000 + 38 * 1000) {
611
0
        sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS, sqlite_handle_,
612
0
                             0x100000);
613
0
    }
614
615
1
    sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
616
1
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
617
1
                            PROJ_SQLITE_pseudo_area_from_swne, nullptr,
618
1
                            nullptr);
619
620
1
    sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
621
1
                            SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
622
1
                            PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
623
1
}
624
625
// ---------------------------------------------------------------------------
626
627
class SQLiteHandleCache {
628
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
629
    bool firstTime_ = true;
630
#endif
631
632
    std::mutex sMutex_{};
633
634
    // Map dbname to SQLiteHandle
635
    lru11::Cache<std::string, std::shared_ptr<SQLiteHandle>> cache_{};
636
637
  public:
638
    static SQLiteHandleCache &get();
639
640
    std::shared_ptr<SQLiteHandle> getHandle(const std::string &path,
641
                                            PJ_CONTEXT *ctx);
642
643
    void clear();
644
645
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
646
    void invalidateHandles();
647
#endif
648
};
649
650
// ---------------------------------------------------------------------------
651
652
824
SQLiteHandleCache &SQLiteHandleCache::get() {
653
    // Global cache
654
824
    static SQLiteHandleCache gSQLiteHandleCache;
655
824
    return gSQLiteHandleCache;
656
824
}
657
658
// ---------------------------------------------------------------------------
659
660
0
void SQLiteHandleCache::clear() {
661
0
    std::lock_guard<std::mutex> lock(sMutex_);
662
0
    cache_.clear();
663
0
}
664
665
// ---------------------------------------------------------------------------
666
667
std::shared_ptr<SQLiteHandle>
668
824
SQLiteHandleCache::getHandle(const std::string &path, PJ_CONTEXT *ctx) {
669
824
    std::lock_guard<std::mutex> lock(sMutex_);
670
671
824
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
672
824
    if (firstTime_) {
673
1
        firstTime_ = false;
674
1
        pthread_atfork(
675
1
            []() {
676
                // This mutex needs to be acquired by 'invalidateHandles()'.
677
                // The forking thread needs to own this mutex during the fork.
678
                // Otherwise there's an opporunity for another thread to own
679
                // the mutex during the fork, leaving the child process unable
680
                // to acquire the mutex in invalidateHandles().
681
0
                SQLiteHandleCache::get().sMutex_.lock();
682
0
            },
683
1
            []() { SQLiteHandleCache::get().sMutex_.unlock(); },
684
1
            []() {
685
0
                SQLiteHandleCache::get().sMutex_.unlock();
686
0
                SQLiteHandleCache::get().invalidateHandles();
687
0
            });
688
1
    }
689
824
#endif
690
691
824
    std::shared_ptr<SQLiteHandle> handle;
692
824
    std::string key = path + ctx->custom_sqlite3_vfs_name;
693
824
    if (!cache_.tryGet(key, handle)) {
694
1
        handle = SQLiteHandle::open(ctx, path);
695
1
        cache_.insert(key, handle);
696
1
    }
697
824
    return handle;
698
824
}
699
700
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
701
// ---------------------------------------------------------------------------
702
703
0
void SQLiteHandleCache::invalidateHandles() {
704
0
    std::lock_guard<std::mutex> lock(sMutex_);
705
0
    const auto lambda =
706
0
        [](const lru11::KeyValuePair<std::string, std::shared_ptr<SQLiteHandle>>
707
0
               &kvp) { kvp.value->invalidate(); };
708
0
    cache_.cwalk(lambda);
709
0
    cache_.clear();
710
0
}
711
#endif
712
713
// ---------------------------------------------------------------------------
714
715
struct DatabaseContext::Private {
716
    Private();
717
    ~Private();
718
719
    void open(const std::string &databasePath, PJ_CONTEXT *ctx);
720
    void setHandle(sqlite3 *sqlite_handle);
721
722
    const std::shared_ptr<SQLiteHandle> &handle();
723
724
824
    PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
725
824
    void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
726
727
    SQLResultSet run(const std::string &sql,
728
                     const ListOfParams &parameters = ListOfParams(),
729
                     bool useMaxFloatPrecision = false);
730
731
    std::vector<std::string> getDatabaseStructure();
732
733
    // cppcheck-suppress functionStatic
734
0
    const std::string &getPath() const { return databasePath_; }
735
736
    void attachExtraDatabases(
737
        const std::vector<std::string> &auxiliaryDatabasePaths);
738
739
    // Mechanism to detect recursion in calls from
740
    // AuthorityFactory::createXXX() -> createFromUserInput() ->
741
    // AuthorityFactory::createXXX()
742
    struct RecursionDetector {
743
        explicit RecursionDetector(const DatabaseContextNNPtr &context)
744
85
            : dbContext_(context) {
745
85
            if (dbContext_->getPrivate()->recLevel_ == 2) {
746
                // Throw exception before incrementing, since the destructor
747
                // will not be called
748
0
                throw FactoryException("Too many recursive calls");
749
0
            }
750
85
            ++dbContext_->getPrivate()->recLevel_;
751
85
        }
752
753
85
        ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
754
755
      private:
756
        DatabaseContextNNPtr dbContext_;
757
    };
758
759
2.33k
    std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
760
2.33k
        return mapCanonicalizeGRFName_;
761
2.33k
    }
762
763
    // cppcheck-suppress functionStatic
764
    common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
765
    // cppcheck-suppress functionStatic
766
    void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
767
768
    // cppcheck-suppress functionStatic
769
    crs::CRSPtr getCRSFromCache(const std::string &code);
770
    // cppcheck-suppress functionStatic
771
    void cache(const std::string &code, const crs::CRSNNPtr &crs);
772
773
    datum::GeodeticReferenceFramePtr
774
    // cppcheck-suppress functionStatic
775
    getGeodeticDatumFromCache(const std::string &code);
776
    // cppcheck-suppress functionStatic
777
    void cache(const std::string &code,
778
               const datum::GeodeticReferenceFrameNNPtr &datum);
779
780
    datum::DatumEnsemblePtr
781
    // cppcheck-suppress functionStatic
782
    getDatumEnsembleFromCache(const std::string &code);
783
    // cppcheck-suppress functionStatic
784
    void cache(const std::string &code,
785
               const datum::DatumEnsembleNNPtr &datumEnsemble);
786
787
    datum::EllipsoidPtr
788
    // cppcheck-suppress functionStatic
789
    getEllipsoidFromCache(const std::string &code);
790
    // cppcheck-suppress functionStatic
791
    void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
792
793
    datum::PrimeMeridianPtr
794
    // cppcheck-suppress functionStatic
795
    getPrimeMeridianFromCache(const std::string &code);
796
    // cppcheck-suppress functionStatic
797
    void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
798
799
    // cppcheck-suppress functionStatic
800
    cs::CoordinateSystemPtr
801
    getCoordinateSystemFromCache(const std::string &code);
802
    // cppcheck-suppress functionStatic
803
    void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
804
805
    // cppcheck-suppress functionStatic
806
    metadata::ExtentPtr getExtentFromCache(const std::string &code);
807
    // cppcheck-suppress functionStatic
808
    void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
809
810
    // cppcheck-suppress functionStatic
811
    bool getCRSToCRSCoordOpFromCache(
812
        const std::string &code,
813
        std::vector<operation::CoordinateOperationNNPtr> &list);
814
    // cppcheck-suppress functionStatic
815
    void cache(const std::string &code,
816
               const std::vector<operation::CoordinateOperationNNPtr> &list);
817
818
    struct GridInfoCache {
819
        std::string fullFilename{};
820
        std::string packageName{};
821
        std::string url{};
822
        bool found = false;
823
        bool directDownload = false;
824
        bool openLicense = false;
825
        bool gridAvailable = false;
826
    };
827
828
    // cppcheck-suppress functionStatic
829
    bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
830
    // cppcheck-suppress functionStatic
831
    void evictGridInfoFromCache(const std::string &code);
832
    // cppcheck-suppress functionStatic
833
    void cache(const std::string &code, const GridInfoCache &info);
834
835
    struct VersionedAuthName {
836
        std::string versionedAuthName{};
837
        std::string authName{};
838
        std::string version{};
839
        int priority = 0;
840
    };
841
    const std::vector<VersionedAuthName> &getCacheAuthNameWithVersion();
842
843
  private:
844
    friend class DatabaseContext;
845
846
    // This is a manual implementation of std::enable_shared_from_this<> that
847
    // avoids publicly deriving from it.
848
    std::weak_ptr<DatabaseContext> self_{};
849
850
    std::string databasePath_{};
851
    std::vector<std::string> auxiliaryDatabasePaths_{};
852
    std::shared_ptr<SQLiteHandle> sqlite_handle_{};
853
    unsigned int queryCounter_ = 0;
854
    std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
855
    PJ_CONTEXT *pjCtxt_ = nullptr;
856
    int recLevel_ = 0;
857
    bool detach_ = false;
858
    std::string lastMetadataValue_{};
859
    std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
860
861
    // Used by startInsertStatementsSession() and related functions
862
    std::string memoryDbForInsertPath_{};
863
    std::unique_ptr<SQLiteHandle> memoryDbHandle_{};
864
865
    using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
866
867
    static constexpr size_t CACHE_SIZE = 128;
868
    LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
869
    LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
870
    LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
871
    LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
872
    LRUCacheOfObjects cacheDatumEnsemble_{CACHE_SIZE};
873
    LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
874
    LRUCacheOfObjects cacheCS_{CACHE_SIZE};
875
    LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
876
    lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
877
        cacheCRSToCrsCoordOp_{CACHE_SIZE};
878
    lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
879
880
    std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
881
882
    lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
883
        CACHE_SIZE};
884
    lru11::Cache<std::string, std::string> cacheNames_{CACHE_SIZE};
885
886
    std::vector<VersionedAuthName> cacheAuthNameWithVersion_{};
887
888
    static void insertIntoCache(LRUCacheOfObjects &cache,
889
                                const std::string &code,
890
                                const util::BaseObjectPtr &obj);
891
892
    static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
893
                             util::BaseObjectPtr &obj);
894
895
    void closeDB() noexcept;
896
897
    void clearCaches();
898
899
    std::string findFreeCode(const std::string &tableName,
900
                             const std::string &authName,
901
                             const std::string &codePrototype);
902
903
    void identify(const DatabaseContextNNPtr &dbContext,
904
                  const cs::CoordinateSystemNNPtr &obj, std::string &authName,
905
                  std::string &code);
906
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
907
                          const cs::CoordinateSystemNNPtr &obj,
908
                          const std::string &ownerType,
909
                          const std::string &ownerAuthName,
910
                          const std::string &ownerCode, std::string &authName,
911
                          std::string &code,
912
                          std::vector<std::string> &sqlStatements);
913
914
    void identify(const DatabaseContextNNPtr &dbContext,
915
                  const common::UnitOfMeasure &obj, std::string &authName,
916
                  std::string &code);
917
    void identifyOrInsert(const DatabaseContextNNPtr &dbContext,
918
                          const common::UnitOfMeasure &unit,
919
                          const std::string &ownerAuthName,
920
                          std::string &authName, std::string &code,
921
                          std::vector<std::string> &sqlStatements);
922
923
    void appendSql(std::vector<std::string> &sqlStatements,
924
                   const std::string &sql);
925
926
    void
927
    identifyOrInsertUsages(const common::ObjectUsageNNPtr &obj,
928
                           const std::string &tableName,
929
                           const std::string &authName, const std::string &code,
930
                           const std::vector<std::string> &allowedAuthorities,
931
                           std::vector<std::string> &sqlStatements);
932
933
    std::vector<std::string>
934
    getInsertStatementsFor(const datum::PrimeMeridianNNPtr &pm,
935
                           const std::string &authName, const std::string &code,
936
                           bool numericCode,
937
                           const std::vector<std::string> &allowedAuthorities);
938
939
    std::vector<std::string>
940
    getInsertStatementsFor(const datum::EllipsoidNNPtr &ellipsoid,
941
                           const std::string &authName, const std::string &code,
942
                           bool numericCode,
943
                           const std::vector<std::string> &allowedAuthorities);
944
945
    std::vector<std::string>
946
    getInsertStatementsFor(const datum::GeodeticReferenceFrameNNPtr &datum,
947
                           const std::string &authName, const std::string &code,
948
                           bool numericCode,
949
                           const std::vector<std::string> &allowedAuthorities);
950
951
    std::vector<std::string>
952
    getInsertStatementsFor(const datum::DatumEnsembleNNPtr &ensemble,
953
                           const std::string &authName, const std::string &code,
954
                           bool numericCode,
955
                           const std::vector<std::string> &allowedAuthorities);
956
957
    std::vector<std::string>
958
    getInsertStatementsFor(const crs::GeodeticCRSNNPtr &crs,
959
                           const std::string &authName, const std::string &code,
960
                           bool numericCode,
961
                           const std::vector<std::string> &allowedAuthorities);
962
963
    std::vector<std::string>
964
    getInsertStatementsFor(const crs::ProjectedCRSNNPtr &crs,
965
                           const std::string &authName, const std::string &code,
966
                           bool numericCode,
967
                           const std::vector<std::string> &allowedAuthorities);
968
969
    std::vector<std::string>
970
    getInsertStatementsFor(const datum::VerticalReferenceFrameNNPtr &datum,
971
                           const std::string &authName, const std::string &code,
972
                           bool numericCode,
973
                           const std::vector<std::string> &allowedAuthorities);
974
975
    std::vector<std::string>
976
    getInsertStatementsFor(const crs::VerticalCRSNNPtr &crs,
977
                           const std::string &authName, const std::string &code,
978
                           bool numericCode,
979
                           const std::vector<std::string> &allowedAuthorities);
980
981
    std::vector<std::string>
982
    getInsertStatementsFor(const crs::CompoundCRSNNPtr &crs,
983
                           const std::string &authName, const std::string &code,
984
                           bool numericCode,
985
                           const std::vector<std::string> &allowedAuthorities);
986
987
    Private(const Private &) = delete;
988
    Private &operator=(const Private &) = delete;
989
};
990
991
// ---------------------------------------------------------------------------
992
993
824
DatabaseContext::Private::Private() = default;
994
995
// ---------------------------------------------------------------------------
996
997
823
DatabaseContext::Private::~Private() {
998
823
    assert(recLevel_ == 0);
999
1000
823
    closeDB();
1001
823
}
1002
1003
// ---------------------------------------------------------------------------
1004
1005
823
void DatabaseContext::Private::closeDB() noexcept {
1006
1007
823
    if (detach_) {
1008
        // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
1009
        // a crash in TEST(factory, attachExtraDatabases_auxiliary)
1010
        // due to possible wrong caching of key info.
1011
        // The bug is specific to using a memory file with shared cache as an
1012
        // auxiliary DB.
1013
        // The fix was likely in 3.8.8
1014
        // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
1015
        // But just after 3.8.2,
1016
        // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
1017
        // also seemed to hide the issue.
1018
        // Detaching a database hides the issue, not sure if it is by chance...
1019
0
        try {
1020
0
            run("DETACH DATABASE db_0");
1021
0
        } catch (...) {
1022
0
        }
1023
0
        detach_ = false;
1024
0
    }
1025
1026
1.14k
    for (auto &pair : mapSqlToStatement_) {
1027
1.14k
        sqlite3_finalize(pair.second);
1028
1.14k
    }
1029
823
    mapSqlToStatement_.clear();
1030
1031
823
    sqlite_handle_.reset();
1032
823
}
1033
1034
// ---------------------------------------------------------------------------
1035
1036
0
void DatabaseContext::Private::clearCaches() {
1037
1038
0
    cacheUOM_.clear();
1039
0
    cacheCRS_.clear();
1040
0
    cacheEllipsoid_.clear();
1041
0
    cacheGeodeticDatum_.clear();
1042
0
    cacheDatumEnsemble_.clear();
1043
0
    cachePrimeMeridian_.clear();
1044
0
    cacheCS_.clear();
1045
0
    cacheExtent_.clear();
1046
0
    cacheCRSToCrsCoordOp_.clear();
1047
0
    cacheGridInfo_.clear();
1048
0
    cacheAllowedAuthorities_.clear();
1049
0
    cacheAliasNames_.clear();
1050
0
    cacheNames_.clear();
1051
0
}
1052
1053
// ---------------------------------------------------------------------------
1054
1055
101k
const std::shared_ptr<SQLiteHandle> &DatabaseContext::Private::handle() {
1056
101k
#ifdef REOPEN_SQLITE_DB_AFTER_FORK
1057
101k
    if (sqlite_handle_ && !sqlite_handle_->isValid()) {
1058
0
        closeDB();
1059
0
        open(databasePath_, pjCtxt_);
1060
0
        if (!auxiliaryDatabasePaths_.empty()) {
1061
0
            attachExtraDatabases(auxiliaryDatabasePaths_);
1062
0
        }
1063
0
    }
1064
101k
#endif
1065
101k
    return sqlite_handle_;
1066
101k
}
1067
1068
// ---------------------------------------------------------------------------
1069
1070
void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
1071
                                               const std::string &code,
1072
6.87k
                                               const util::BaseObjectPtr &obj) {
1073
6.87k
    cache.insert(code, obj);
1074
6.87k
}
1075
1076
// ---------------------------------------------------------------------------
1077
1078
void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
1079
                                            const std::string &code,
1080
46.6k
                                            util::BaseObjectPtr &obj) {
1081
46.6k
    cache.tryGet(code, obj);
1082
46.6k
}
1083
1084
// ---------------------------------------------------------------------------
1085
1086
bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
1087
    const std::string &code,
1088
0
    std::vector<operation::CoordinateOperationNNPtr> &list) {
1089
0
    return cacheCRSToCrsCoordOp_.tryGet(code, list);
1090
0
}
1091
1092
// ---------------------------------------------------------------------------
1093
1094
void DatabaseContext::Private::cache(
1095
    const std::string &code,
1096
0
    const std::vector<operation::CoordinateOperationNNPtr> &list) {
1097
0
    cacheCRSToCrsCoordOp_.insert(code, list);
1098
0
}
1099
1100
// ---------------------------------------------------------------------------
1101
1102
18.4k
crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
1103
18.4k
    util::BaseObjectPtr obj;
1104
18.4k
    getFromCache(cacheCRS_, code, obj);
1105
18.4k
    return std::static_pointer_cast<crs::CRS>(obj);
1106
18.4k
}
1107
1108
// ---------------------------------------------------------------------------
1109
1110
void DatabaseContext::Private::cache(const std::string &code,
1111
5.22k
                                     const crs::CRSNNPtr &crs) {
1112
5.22k
    insertIntoCache(cacheCRS_, code, crs.as_nullable());
1113
5.22k
}
1114
1115
// ---------------------------------------------------------------------------
1116
1117
common::UnitOfMeasurePtr
1118
12.4k
DatabaseContext::Private::getUOMFromCache(const std::string &code) {
1119
12.4k
    util::BaseObjectPtr obj;
1120
12.4k
    getFromCache(cacheUOM_, code, obj);
1121
12.4k
    return std::static_pointer_cast<common::UnitOfMeasure>(obj);
1122
12.4k
}
1123
1124
// ---------------------------------------------------------------------------
1125
1126
void DatabaseContext::Private::cache(const std::string &code,
1127
82
                                     const common::UnitOfMeasureNNPtr &uom) {
1128
82
    insertIntoCache(cacheUOM_, code, uom.as_nullable());
1129
82
}
1130
1131
// ---------------------------------------------------------------------------
1132
1133
datum::GeodeticReferenceFramePtr
1134
3.94k
DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
1135
3.94k
    util::BaseObjectPtr obj;
1136
3.94k
    getFromCache(cacheGeodeticDatum_, code, obj);
1137
3.94k
    return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
1138
3.94k
}
1139
1140
// ---------------------------------------------------------------------------
1141
1142
void DatabaseContext::Private::cache(
1143
1.30k
    const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
1144
1.30k
    insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
1145
1.30k
}
1146
1147
// ---------------------------------------------------------------------------
1148
1149
datum::DatumEnsemblePtr
1150
4.03k
DatabaseContext::Private::getDatumEnsembleFromCache(const std::string &code) {
1151
4.03k
    util::BaseObjectPtr obj;
1152
4.03k
    getFromCache(cacheDatumEnsemble_, code, obj);
1153
4.03k
    return std::static_pointer_cast<datum::DatumEnsemble>(obj);
1154
4.03k
}
1155
1156
// ---------------------------------------------------------------------------
1157
1158
void DatabaseContext::Private::cache(
1159
2
    const std::string &code, const datum::DatumEnsembleNNPtr &datumEnsemble) {
1160
2
    insertIntoCache(cacheDatumEnsemble_, code, datumEnsemble.as_nullable());
1161
2
}
1162
1163
// ---------------------------------------------------------------------------
1164
1165
datum::EllipsoidPtr
1166
1.37k
DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
1167
1.37k
    util::BaseObjectPtr obj;
1168
1.37k
    getFromCache(cacheEllipsoid_, code, obj);
1169
1.37k
    return std::static_pointer_cast<datum::Ellipsoid>(obj);
1170
1.37k
}
1171
1172
// ---------------------------------------------------------------------------
1173
1174
void DatabaseContext::Private::cache(const std::string &code,
1175
132
                                     const datum::EllipsoidNNPtr &ellps) {
1176
132
    insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
1177
132
}
1178
1179
// ---------------------------------------------------------------------------
1180
1181
datum::PrimeMeridianPtr
1182
1.30k
DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
1183
1.30k
    util::BaseObjectPtr obj;
1184
1.30k
    getFromCache(cachePrimeMeridian_, code, obj);
1185
1.30k
    return std::static_pointer_cast<datum::PrimeMeridian>(obj);
1186
1.30k
}
1187
1188
// ---------------------------------------------------------------------------
1189
1190
void DatabaseContext::Private::cache(const std::string &code,
1191
42
                                     const datum::PrimeMeridianNNPtr &pm) {
1192
42
    insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
1193
42
}
1194
1195
// ---------------------------------------------------------------------------
1196
1197
cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
1198
5.14k
    const std::string &code) {
1199
5.14k
    util::BaseObjectPtr obj;
1200
5.14k
    getFromCache(cacheCS_, code, obj);
1201
5.14k
    return std::static_pointer_cast<cs::CoordinateSystem>(obj);
1202
5.14k
}
1203
1204
// ---------------------------------------------------------------------------
1205
1206
void DatabaseContext::Private::cache(const std::string &code,
1207
84
                                     const cs::CoordinateSystemNNPtr &cs) {
1208
84
    insertIntoCache(cacheCS_, code, cs.as_nullable());
1209
84
}
1210
1211
// ---------------------------------------------------------------------------
1212
1213
metadata::ExtentPtr
1214
0
DatabaseContext::Private::getExtentFromCache(const std::string &code) {
1215
0
    util::BaseObjectPtr obj;
1216
0
    getFromCache(cacheExtent_, code, obj);
1217
0
    return std::static_pointer_cast<metadata::Extent>(obj);
1218
0
}
1219
1220
// ---------------------------------------------------------------------------
1221
1222
void DatabaseContext::Private::cache(const std::string &code,
1223
0
                                     const metadata::ExtentNNPtr &extent) {
1224
0
    insertIntoCache(cacheExtent_, code, extent.as_nullable());
1225
0
}
1226
1227
// ---------------------------------------------------------------------------
1228
1229
bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
1230
0
                                                    GridInfoCache &info) {
1231
0
    return cacheGridInfo_.tryGet(code, info);
1232
0
}
1233
1234
// ---------------------------------------------------------------------------
1235
1236
0
void DatabaseContext::Private::evictGridInfoFromCache(const std::string &code) {
1237
0
    cacheGridInfo_.remove(code);
1238
0
}
1239
1240
// ---------------------------------------------------------------------------
1241
1242
void DatabaseContext::Private::cache(const std::string &code,
1243
0
                                     const GridInfoCache &info) {
1244
0
    cacheGridInfo_.insert(code, info);
1245
0
}
1246
1247
// ---------------------------------------------------------------------------
1248
1249
void DatabaseContext::Private::open(const std::string &databasePath,
1250
824
                                    PJ_CONTEXT *ctx) {
1251
824
    if (!ctx) {
1252
0
        ctx = pj_get_default_ctx();
1253
0
    }
1254
1255
824
    setPjCtxt(ctx);
1256
824
    std::string path(databasePath);
1257
824
    if (path.empty()) {
1258
824
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
1259
824
        path.resize(2048);
1260
824
        const bool found =
1261
824
            pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
1262
824
        path.resize(strlen(path.c_str()));
1263
824
        if (!found)
1264
824
#endif
1265
824
        {
1266
824
#ifdef EMBED_RESOURCE_FILES
1267
824
            path = EMBEDDED_PROJ_DB;
1268
#else
1269
            throw FactoryException("Cannot find proj.db");
1270
#endif
1271
824
        }
1272
824
    }
1273
1274
824
    sqlite_handle_ = SQLiteHandleCache::get().getHandle(path, ctx);
1275
1276
824
    databasePath_ = sqlite_handle_->path();
1277
824
}
1278
1279
// ---------------------------------------------------------------------------
1280
1281
0
void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
1282
1283
0
    assert(sqlite_handle);
1284
0
    assert(!sqlite_handle_);
1285
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(sqlite_handle, false, 0, 0);
1286
0
}
1287
1288
// ---------------------------------------------------------------------------
1289
1290
0
std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
1291
0
    const std::string dbNamePrefix(auxiliaryDatabasePaths_.empty() &&
1292
0
                                           memoryDbForInsertPath_.empty()
1293
0
                                       ? ""
1294
0
                                       : "db_0.");
1295
0
    const auto sqlBegin("SELECT sql||';' FROM " + dbNamePrefix +
1296
0
                        "sqlite_master WHERE type = ");
1297
0
    const char *tableType = "'table' AND name NOT LIKE 'sqlite_stat%'";
1298
0
    const char *const objectTypes[] = {tableType, "'view'", "'trigger'"};
1299
0
    std::vector<std::string> res;
1300
0
    for (const auto &objectType : objectTypes) {
1301
0
        const auto sqlRes = run(sqlBegin + objectType);
1302
0
        for (const auto &row : sqlRes) {
1303
0
            res.emplace_back(row[0]);
1304
0
        }
1305
0
    }
1306
0
    if (sqlite_handle_->getLayoutVersionMajor() > 0) {
1307
0
        res.emplace_back(
1308
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MAJOR'," +
1309
0
            toString(sqlite_handle_->getLayoutVersionMajor()) + ");");
1310
0
        res.emplace_back(
1311
0
            "INSERT INTO metadata VALUES('DATABASE.LAYOUT.VERSION.MINOR'," +
1312
0
            toString(sqlite_handle_->getLayoutVersionMinor()) + ");");
1313
0
    }
1314
0
    return res;
1315
0
}
1316
1317
// ---------------------------------------------------------------------------
1318
1319
void DatabaseContext::Private::attachExtraDatabases(
1320
0
    const std::vector<std::string> &auxiliaryDatabasePaths) {
1321
1322
0
    auto l_handle = handle();
1323
0
    assert(l_handle);
1324
1325
0
    auto tables = run("SELECT name, type, sql FROM sqlite_master WHERE type IN "
1326
0
                      "('table', 'view') "
1327
0
                      "AND name NOT LIKE 'sqlite_stat%'");
1328
1329
0
    struct TableStructure {
1330
0
        std::string name{};
1331
0
        bool isTable = false;
1332
0
        std::string sql{};
1333
0
        std::vector<std::string> columns{};
1334
0
    };
1335
0
    std::vector<TableStructure> tablesStructure;
1336
0
    for (const auto &rowTable : tables) {
1337
0
        TableStructure tableStructure;
1338
0
        tableStructure.name = rowTable[0];
1339
0
        tableStructure.isTable = rowTable[1] == "table";
1340
0
        tableStructure.sql = rowTable[2];
1341
0
        auto tableInfo =
1342
0
            run("PRAGMA table_info(\"" +
1343
0
                replaceAll(tableStructure.name, "\"", "\"\"") + "\")");
1344
0
        for (const auto &rowCol : tableInfo) {
1345
0
            const auto &colName = rowCol[1];
1346
0
            tableStructure.columns.push_back(colName);
1347
0
        }
1348
0
        tablesStructure.push_back(std::move(tableStructure));
1349
0
    }
1350
1351
0
    const int nLayoutVersionMajor = l_handle->getLayoutVersionMajor();
1352
0
    const int nLayoutVersionMinor = l_handle->getLayoutVersionMinor();
1353
1354
0
    closeDB();
1355
0
    if (auxiliaryDatabasePaths.empty()) {
1356
0
        open(databasePath_, pjCtxt());
1357
0
        return;
1358
0
    }
1359
1360
0
    sqlite3 *sqlite_handle = nullptr;
1361
0
    sqlite3_open_v2(
1362
0
        ":memory:", &sqlite_handle,
1363
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_URI, nullptr);
1364
0
    if (!sqlite_handle) {
1365
0
        throw FactoryException("cannot create in memory database");
1366
0
    }
1367
0
    sqlite_handle_ = SQLiteHandle::initFromExisting(
1368
0
        sqlite_handle, true, nLayoutVersionMajor, nLayoutVersionMinor);
1369
0
    l_handle = sqlite_handle_;
1370
1371
0
    run("ATTACH DATABASE ? AS db_0", {databasePath_});
1372
0
    detach_ = true;
1373
0
    int count = 1;
1374
0
    for (const auto &otherDbPath : auxiliaryDatabasePaths) {
1375
0
        const auto attachedDbName("db_" + toString(static_cast<int>(count)));
1376
0
        std::string sql = "ATTACH DATABASE ? AS ";
1377
0
        sql += attachedDbName;
1378
0
        count++;
1379
0
        run(sql, {otherDbPath});
1380
1381
0
        l_handle->checkDatabaseLayout(databasePath_, otherDbPath,
1382
0
                                      attachedDbName + '.');
1383
0
    }
1384
1385
0
    for (const auto &tableStructure : tablesStructure) {
1386
0
        if (tableStructure.isTable) {
1387
0
            std::string sql("CREATE TEMP VIEW ");
1388
0
            sql += tableStructure.name;
1389
0
            sql += " AS ";
1390
0
            for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
1391
0
                std::string selectFromAux("SELECT ");
1392
0
                bool firstCol = true;
1393
0
                for (const auto &colName : tableStructure.columns) {
1394
0
                    if (!firstCol) {
1395
0
                        selectFromAux += ", ";
1396
0
                    }
1397
0
                    firstCol = false;
1398
0
                    selectFromAux += colName;
1399
0
                }
1400
0
                selectFromAux += " FROM db_";
1401
0
                selectFromAux += toString(static_cast<int>(i));
1402
0
                selectFromAux += ".";
1403
0
                selectFromAux += tableStructure.name;
1404
1405
0
                try {
1406
                    // Check that the request will succeed. In case of 'sparse'
1407
                    // databases...
1408
0
                    run(selectFromAux + " LIMIT 0");
1409
1410
0
                    if (i > 0) {
1411
0
                        if (tableStructure.name == "conversion_method")
1412
0
                            sql += " UNION ";
1413
0
                        else
1414
0
                            sql += " UNION ALL ";
1415
0
                    }
1416
0
                    sql += selectFromAux;
1417
0
                } catch (const std::exception &) {
1418
0
                }
1419
0
            }
1420
0
            run(sql);
1421
0
        } else {
1422
0
            run(replaceAll(tableStructure.sql, "CREATE VIEW",
1423
0
                           "CREATE TEMP VIEW"));
1424
0
        }
1425
0
    }
1426
0
}
1427
1428
// ---------------------------------------------------------------------------
1429
1430
SQLResultSet DatabaseContext::Private::run(const std::string &sql,
1431
                                           const ListOfParams &parameters,
1432
101k
                                           bool useMaxFloatPrecision) {
1433
1434
101k
    auto l_handle = handle();
1435
101k
    assert(l_handle);
1436
1437
101k
    sqlite3_stmt *stmt = nullptr;
1438
101k
    auto iter = mapSqlToStatement_.find(sql);
1439
101k
    if (iter != mapSqlToStatement_.end()) {
1440
100k
        stmt = iter->second;
1441
100k
        sqlite3_reset(stmt);
1442
100k
    } else {
1443
1.20k
        if (sqlite3_prepare_v2(l_handle->handle(), sql.c_str(),
1444
1.20k
                               static_cast<int>(sql.size()), &stmt,
1445
1.20k
                               nullptr) != SQLITE_OK) {
1446
0
            throw FactoryException("SQLite error on " + sql + ": " +
1447
0
                                   sqlite3_errmsg(l_handle->handle()));
1448
0
        }
1449
1.20k
        mapSqlToStatement_.insert(
1450
1.20k
            std::pair<std::string, sqlite3_stmt *>(sql, stmt));
1451
1.20k
    }
1452
1453
101k
    ++queryCounter_;
1454
1455
101k
    return l_handle->run(stmt, sql, parameters, useMaxFloatPrecision);
1456
101k
}
1457
1458
// ---------------------------------------------------------------------------
1459
1460
0
static std::string formatStatement(const char *fmt, ...) {
1461
0
    std::string res;
1462
0
    va_list args;
1463
0
    va_start(args, fmt);
1464
0
    for (int i = 0; fmt[i] != '\0'; ++i) {
1465
0
        if (fmt[i] == '%') {
1466
0
            if (fmt[i + 1] == '%') {
1467
0
                res += '%';
1468
0
            } else if (fmt[i + 1] == 'q') {
1469
0
                const char *arg = va_arg(args, const char *);
1470
0
                for (int j = 0; arg[j] != '\0'; ++j) {
1471
0
                    if (arg[j] == '\'')
1472
0
                        res += arg[j];
1473
0
                    res += arg[j];
1474
0
                }
1475
0
            } else if (fmt[i + 1] == 'Q') {
1476
0
                const char *arg = va_arg(args, const char *);
1477
0
                if (arg == nullptr)
1478
0
                    res += "NULL";
1479
0
                else {
1480
0
                    res += '\'';
1481
0
                    for (int j = 0; arg[j] != '\0'; ++j) {
1482
0
                        if (arg[j] == '\'')
1483
0
                            res += arg[j];
1484
0
                        res += arg[j];
1485
0
                    }
1486
0
                    res += '\'';
1487
0
                }
1488
0
            } else if (fmt[i + 1] == 's') {
1489
0
                const char *arg = va_arg(args, const char *);
1490
0
                res += arg;
1491
0
            } else if (fmt[i + 1] == 'f') {
1492
0
                const double arg = va_arg(args, double);
1493
0
                res += toString(arg);
1494
0
            } else if (fmt[i + 1] == 'd') {
1495
0
                const int arg = va_arg(args, int);
1496
0
                res += toString(arg);
1497
0
            } else {
1498
0
                va_end(args);
1499
0
                throw FactoryException(
1500
0
                    "Unsupported formatter in formatStatement()");
1501
0
            }
1502
0
            ++i;
1503
0
        } else {
1504
0
            res += fmt[i];
1505
0
        }
1506
0
    }
1507
0
    va_end(args);
1508
0
    return res;
1509
0
}
1510
1511
// ---------------------------------------------------------------------------
1512
1513
void DatabaseContext::Private::appendSql(
1514
0
    std::vector<std::string> &sqlStatements, const std::string &sql) {
1515
0
    sqlStatements.emplace_back(sql);
1516
0
    char *errMsg = nullptr;
1517
0
    if (sqlite3_exec(memoryDbHandle_->handle(), sql.c_str(), nullptr, nullptr,
1518
0
                     &errMsg) != SQLITE_OK) {
1519
0
        std::string s("Cannot execute " + sql);
1520
0
        if (errMsg) {
1521
0
            s += " : ";
1522
0
            s += errMsg;
1523
0
        }
1524
0
        sqlite3_free(errMsg);
1525
0
        throw FactoryException(s);
1526
0
    }
1527
0
    sqlite3_free(errMsg);
1528
0
}
1529
1530
// ---------------------------------------------------------------------------
1531
1532
static void identifyFromNameOrCode(
1533
    const DatabaseContextNNPtr &dbContext,
1534
    const std::vector<std::string> &allowedAuthorities,
1535
    const std::string &authNameParent, const common::IdentifiedObjectNNPtr &obj,
1536
    std::function<std::shared_ptr<util::IComparable>(
1537
        const AuthorityFactoryNNPtr &authFactory, const std::string &)>
1538
        instantiateFunc,
1539
    AuthorityFactory::ObjectType objType, std::string &authName,
1540
0
    std::string &code) {
1541
1542
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
1543
0
    allowedAuthoritiesTmp.emplace_back(authNameParent);
1544
1545
0
    for (const auto &id : obj->identifiers()) {
1546
0
        try {
1547
0
            const auto &idAuthName = *(id->codeSpace());
1548
0
            if (std::find(allowedAuthoritiesTmp.begin(),
1549
0
                          allowedAuthoritiesTmp.end(),
1550
0
                          idAuthName) != allowedAuthoritiesTmp.end()) {
1551
0
                const auto factory =
1552
0
                    AuthorityFactory::create(dbContext, idAuthName);
1553
0
                if (instantiateFunc(factory, id->code())
1554
0
                        ->isEquivalentTo(
1555
0
                            obj.get(),
1556
0
                            util::IComparable::Criterion::EQUIVALENT)) {
1557
0
                    authName = idAuthName;
1558
0
                    code = id->code();
1559
0
                    return;
1560
0
                }
1561
0
            }
1562
0
        } catch (const std::exception &) {
1563
0
        }
1564
0
    }
1565
1566
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
1567
0
        const auto factory =
1568
0
            AuthorityFactory::create(dbContext, allowedAuthority);
1569
0
        const auto candidates =
1570
0
            factory->createObjectsFromName(obj->nameStr(), {objType}, false, 0);
1571
0
        for (const auto &candidate : candidates) {
1572
0
            const auto &ids = candidate->identifiers();
1573
0
            if (!ids.empty() &&
1574
0
                candidate->isEquivalentTo(
1575
0
                    obj.get(), util::IComparable::Criterion::EQUIVALENT)) {
1576
0
                const auto &id = ids.front();
1577
0
                authName = *(id->codeSpace());
1578
0
                code = id->code();
1579
0
                return;
1580
0
            }
1581
0
        }
1582
0
    }
1583
0
}
1584
1585
// ---------------------------------------------------------------------------
1586
1587
static void
1588
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1589
                       const std::vector<std::string> &allowedAuthorities,
1590
                       const std::string &authNameParent,
1591
                       const datum::DatumEnsembleNNPtr &obj,
1592
0
                       std::string &authName, std::string &code) {
1593
0
    const char *type = "geodetic_datum";
1594
0
    if (!obj->datums().empty() &&
1595
0
        dynamic_cast<const datum::VerticalReferenceFrame *>(
1596
0
            obj->datums().front().get())) {
1597
0
        type = "vertical_datum";
1598
0
    }
1599
0
    const auto instantiateFunc =
1600
0
        [&type](const AuthorityFactoryNNPtr &authFactory,
1601
0
                const std::string &lCode) {
1602
0
            return util::nn_static_pointer_cast<util::IComparable>(
1603
0
                authFactory->createDatumEnsemble(lCode, type));
1604
0
        };
1605
0
    identifyFromNameOrCode(
1606
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1607
0
        AuthorityFactory::ObjectType::DATUM_ENSEMBLE, authName, code);
1608
0
}
1609
1610
// ---------------------------------------------------------------------------
1611
1612
static void
1613
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1614
                       const std::vector<std::string> &allowedAuthorities,
1615
                       const std::string &authNameParent,
1616
                       const datum::GeodeticReferenceFrameNNPtr &obj,
1617
0
                       std::string &authName, std::string &code) {
1618
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1619
0
                                    const std::string &lCode) {
1620
0
        return util::nn_static_pointer_cast<util::IComparable>(
1621
0
            authFactory->createGeodeticDatum(lCode));
1622
0
    };
1623
0
    identifyFromNameOrCode(
1624
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1625
0
        AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME, authName, code);
1626
0
}
1627
1628
// ---------------------------------------------------------------------------
1629
1630
static void
1631
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1632
                       const std::vector<std::string> &allowedAuthorities,
1633
                       const std::string &authNameParent,
1634
                       const datum::EllipsoidNNPtr &obj, std::string &authName,
1635
0
                       std::string &code) {
1636
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1637
0
                                    const std::string &lCode) {
1638
0
        return util::nn_static_pointer_cast<util::IComparable>(
1639
0
            authFactory->createEllipsoid(lCode));
1640
0
    };
1641
0
    identifyFromNameOrCode(
1642
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1643
0
        AuthorityFactory::ObjectType::ELLIPSOID, authName, code);
1644
0
}
1645
1646
// ---------------------------------------------------------------------------
1647
1648
static void
1649
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1650
                       const std::vector<std::string> &allowedAuthorities,
1651
                       const std::string &authNameParent,
1652
                       const datum::PrimeMeridianNNPtr &obj,
1653
0
                       std::string &authName, std::string &code) {
1654
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1655
0
                                    const std::string &lCode) {
1656
0
        return util::nn_static_pointer_cast<util::IComparable>(
1657
0
            authFactory->createPrimeMeridian(lCode));
1658
0
    };
1659
0
    identifyFromNameOrCode(
1660
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1661
0
        AuthorityFactory::ObjectType::PRIME_MERIDIAN, authName, code);
1662
0
}
1663
1664
// ---------------------------------------------------------------------------
1665
1666
static void
1667
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1668
                       const std::vector<std::string> &allowedAuthorities,
1669
                       const std::string &authNameParent,
1670
                       const datum::VerticalReferenceFrameNNPtr &obj,
1671
0
                       std::string &authName, std::string &code) {
1672
0
    const auto instantiateFunc = [](const AuthorityFactoryNNPtr &authFactory,
1673
0
                                    const std::string &lCode) {
1674
0
        return util::nn_static_pointer_cast<util::IComparable>(
1675
0
            authFactory->createVerticalDatum(lCode));
1676
0
    };
1677
0
    identifyFromNameOrCode(
1678
0
        dbContext, allowedAuthorities, authNameParent, obj, instantiateFunc,
1679
0
        AuthorityFactory::ObjectType::VERTICAL_REFERENCE_FRAME, authName, code);
1680
0
}
1681
1682
// ---------------------------------------------------------------------------
1683
1684
static void
1685
identifyFromNameOrCode(const DatabaseContextNNPtr &dbContext,
1686
                       const std::vector<std::string> &allowedAuthorities,
1687
                       const std::string &authNameParent,
1688
                       const datum::DatumNNPtr &obj, std::string &authName,
1689
0
                       std::string &code) {
1690
0
    if (const auto geodeticDatum =
1691
0
            util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(obj)) {
1692
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1693
0
                               NN_NO_CHECK(geodeticDatum), authName, code);
1694
0
    } else if (const auto verticalDatum =
1695
0
                   util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
1696
0
                       obj)) {
1697
0
        identifyFromNameOrCode(dbContext, allowedAuthorities, authNameParent,
1698
0
                               NN_NO_CHECK(verticalDatum), authName, code);
1699
0
    } else {
1700
0
        throw FactoryException("Unhandled type of datum");
1701
0
    }
1702
0
}
1703
1704
// ---------------------------------------------------------------------------
1705
1706
0
static const char *getCSDatabaseType(const cs::CoordinateSystemNNPtr &obj) {
1707
0
    if (dynamic_cast<const cs::EllipsoidalCS *>(obj.get())) {
1708
0
        return CS_TYPE_ELLIPSOIDAL;
1709
0
    } else if (dynamic_cast<const cs::CartesianCS *>(obj.get())) {
1710
0
        return CS_TYPE_CARTESIAN;
1711
0
    } else if (dynamic_cast<const cs::VerticalCS *>(obj.get())) {
1712
0
        return CS_TYPE_VERTICAL;
1713
0
    }
1714
0
    return nullptr;
1715
0
}
1716
1717
// ---------------------------------------------------------------------------
1718
1719
std::string
1720
DatabaseContext::Private::findFreeCode(const std::string &tableName,
1721
                                       const std::string &authName,
1722
0
                                       const std::string &codePrototype) {
1723
0
    std::string code(codePrototype);
1724
0
    if (run("SELECT 1 FROM " + tableName + " WHERE auth_name = ? AND code = ?",
1725
0
            {authName, code})
1726
0
            .empty()) {
1727
0
        return code;
1728
0
    }
1729
1730
0
    for (int counter = 2; counter < 10; counter++) {
1731
0
        code = codePrototype + '_' + toString(counter);
1732
0
        if (run("SELECT 1 FROM " + tableName +
1733
0
                    " WHERE auth_name = ? AND code = ?",
1734
0
                {authName, code})
1735
0
                .empty()) {
1736
0
            return code;
1737
0
        }
1738
0
    }
1739
1740
    // shouldn't happen hopefully...
1741
0
    throw FactoryException("Cannot insert " + tableName +
1742
0
                           ": too many similar codes");
1743
0
}
1744
1745
// ---------------------------------------------------------------------------
1746
1747
0
static const char *getUnitDatabaseType(const common::UnitOfMeasure &unit) {
1748
0
    switch (unit.type()) {
1749
0
    case common::UnitOfMeasure::Type::LINEAR:
1750
0
        return "length";
1751
1752
0
    case common::UnitOfMeasure::Type::ANGULAR:
1753
0
        return "angle";
1754
1755
0
    case common::UnitOfMeasure::Type::SCALE:
1756
0
        return "scale";
1757
1758
0
    case common::UnitOfMeasure::Type::TIME:
1759
0
        return "time";
1760
1761
0
    default:
1762
0
        break;
1763
0
    }
1764
0
    return nullptr;
1765
0
}
1766
1767
// ---------------------------------------------------------------------------
1768
1769
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1770
                                        const common::UnitOfMeasure &obj,
1771
                                        std::string &authName,
1772
0
                                        std::string &code) {
1773
    // Identify quickly a few well-known units
1774
0
    const double convFactor = obj.conversionToSI();
1775
0
    switch (obj.type()) {
1776
0
    case common::UnitOfMeasure::Type::LINEAR: {
1777
0
        if (convFactor == 1.0) {
1778
0
            authName = metadata::Identifier::EPSG;
1779
0
            code = "9001";
1780
0
            return;
1781
0
        }
1782
0
        break;
1783
0
    }
1784
0
    case common::UnitOfMeasure::Type::ANGULAR: {
1785
0
        constexpr double CONV_FACTOR_DEGREE = 1.74532925199432781271e-02;
1786
0
        if (std::abs(convFactor - CONV_FACTOR_DEGREE) <=
1787
0
            1e-10 * CONV_FACTOR_DEGREE) {
1788
0
            authName = metadata::Identifier::EPSG;
1789
0
            code = "9102";
1790
0
            return;
1791
0
        }
1792
0
        break;
1793
0
    }
1794
0
    case common::UnitOfMeasure::Type::SCALE: {
1795
0
        if (convFactor == 1.0) {
1796
0
            authName = metadata::Identifier::EPSG;
1797
0
            code = "9201";
1798
0
            return;
1799
0
        }
1800
0
        break;
1801
0
    }
1802
0
    default:
1803
0
        break;
1804
0
    }
1805
1806
0
    std::string sql("SELECT auth_name, code FROM unit_of_measure "
1807
0
                    "WHERE abs(conv_factor - ?) <= 1e-10 * conv_factor");
1808
0
    ListOfParams params{convFactor};
1809
0
    const char *type = getUnitDatabaseType(obj);
1810
0
    if (type) {
1811
0
        sql += " AND type = ?";
1812
0
        params.emplace_back(std::string(type));
1813
0
    }
1814
0
    sql += " ORDER BY auth_name, code";
1815
0
    const auto res = run(sql, params);
1816
0
    for (const auto &row : res) {
1817
0
        const auto &rowAuthName = row[0];
1818
0
        const auto &rowCode = row[1];
1819
0
        const auto tmpAuthFactory =
1820
0
            AuthorityFactory::create(dbContext, rowAuthName);
1821
0
        try {
1822
0
            tmpAuthFactory->createUnitOfMeasure(rowCode);
1823
0
            authName = rowAuthName;
1824
0
            code = rowCode;
1825
0
            return;
1826
0
        } catch (const std::exception &) {
1827
0
        }
1828
0
    }
1829
0
}
1830
1831
// ---------------------------------------------------------------------------
1832
1833
void DatabaseContext::Private::identifyOrInsert(
1834
    const DatabaseContextNNPtr &dbContext, const common::UnitOfMeasure &unit,
1835
    const std::string &ownerAuthName, std::string &authName, std::string &code,
1836
0
    std::vector<std::string> &sqlStatements) {
1837
0
    authName = unit.codeSpace();
1838
0
    code = unit.code();
1839
0
    if (authName.empty()) {
1840
0
        identify(dbContext, unit, authName, code);
1841
0
    }
1842
0
    if (!authName.empty()) {
1843
0
        return;
1844
0
    }
1845
0
    const char *type = getUnitDatabaseType(unit);
1846
0
    if (type == nullptr) {
1847
0
        throw FactoryException("Cannot insert this type of UnitOfMeasure");
1848
0
    }
1849
1850
    // Insert new record
1851
0
    authName = ownerAuthName;
1852
0
    const std::string codePrototype(replaceAll(toupper(unit.name()), " ", "_"));
1853
0
    code = findFreeCode("unit_of_measure", authName, codePrototype);
1854
1855
0
    const auto sql = formatStatement(
1856
0
        "INSERT INTO unit_of_measure VALUES('%q','%q','%q','%q',%f,NULL,0);",
1857
0
        authName.c_str(), code.c_str(), unit.name().c_str(), type,
1858
0
        unit.conversionToSI());
1859
0
    appendSql(sqlStatements, sql);
1860
0
}
1861
1862
// ---------------------------------------------------------------------------
1863
1864
void DatabaseContext::Private::identify(const DatabaseContextNNPtr &dbContext,
1865
                                        const cs::CoordinateSystemNNPtr &obj,
1866
                                        std::string &authName,
1867
0
                                        std::string &code) {
1868
1869
0
    const auto &axisList = obj->axisList();
1870
0
    if (axisList.size() == 1U &&
1871
0
        axisList[0]->unit()._isEquivalentTo(UnitOfMeasure::METRE) &&
1872
0
        &(axisList[0]->direction()) == &cs::AxisDirection::UP &&
1873
0
        (axisList[0]->nameStr() == "Up" ||
1874
0
         axisList[0]->nameStr() == "Gravity-related height")) {
1875
        // preferred coordinate system for gravity-related height
1876
0
        authName = metadata::Identifier::EPSG;
1877
0
        code = "6499";
1878
0
        return;
1879
0
    }
1880
1881
0
    std::string sql(
1882
0
        "SELECT auth_name, code FROM coordinate_system WHERE dimension = ?");
1883
0
    ListOfParams params{static_cast<int>(axisList.size())};
1884
0
    const char *type = getCSDatabaseType(obj);
1885
0
    if (type) {
1886
0
        sql += " AND type = ?";
1887
0
        params.emplace_back(std::string(type));
1888
0
    }
1889
0
    sql += " ORDER BY auth_name, code";
1890
0
    const auto res = run(sql, params);
1891
0
    for (const auto &row : res) {
1892
0
        const auto &rowAuthName = row[0];
1893
0
        const auto &rowCode = row[1];
1894
0
        const auto tmpAuthFactory =
1895
0
            AuthorityFactory::create(dbContext, rowAuthName);
1896
0
        try {
1897
0
            const auto cs = tmpAuthFactory->createCoordinateSystem(rowCode);
1898
0
            if (cs->_isEquivalentTo(obj.get(),
1899
0
                                    util::IComparable::Criterion::EQUIVALENT)) {
1900
0
                authName = rowAuthName;
1901
0
                code = rowCode;
1902
0
                if (authName == metadata::Identifier::EPSG && code == "4400") {
1903
                    // preferred coordinate system for cartesian
1904
                    // Easting, Northing
1905
0
                    return;
1906
0
                }
1907
0
                if (authName == metadata::Identifier::EPSG && code == "6422") {
1908
                    // preferred coordinate system for geographic lat, long
1909
0
                    return;
1910
0
                }
1911
0
                if (authName == metadata::Identifier::EPSG && code == "6423") {
1912
                    // preferred coordinate system for geographic lat, long, h
1913
0
                    return;
1914
0
                }
1915
0
            }
1916
0
        } catch (const std::exception &) {
1917
0
        }
1918
0
    }
1919
0
}
1920
1921
// ---------------------------------------------------------------------------
1922
1923
void DatabaseContext::Private::identifyOrInsert(
1924
    const DatabaseContextNNPtr &dbContext, const cs::CoordinateSystemNNPtr &obj,
1925
    const std::string &ownerType, const std::string &ownerAuthName,
1926
    const std::string &ownerCode, std::string &authName, std::string &code,
1927
0
    std::vector<std::string> &sqlStatements) {
1928
1929
0
    identify(dbContext, obj, authName, code);
1930
0
    if (!authName.empty()) {
1931
0
        return;
1932
0
    }
1933
1934
0
    const char *type = getCSDatabaseType(obj);
1935
0
    if (type == nullptr) {
1936
0
        throw FactoryException("Cannot insert this type of CoordinateSystem");
1937
0
    }
1938
1939
    // Insert new record in coordinate_system
1940
0
    authName = ownerAuthName;
1941
0
    const std::string codePrototype("CS_" + ownerType + '_' + ownerCode);
1942
0
    code = findFreeCode("coordinate_system", authName, codePrototype);
1943
1944
0
    const auto &axisList = obj->axisList();
1945
0
    {
1946
0
        const auto sql = formatStatement(
1947
0
            "INSERT INTO coordinate_system VALUES('%q','%q','%q',%d);",
1948
0
            authName.c_str(), code.c_str(), type,
1949
0
            static_cast<int>(axisList.size()));
1950
0
        appendSql(sqlStatements, sql);
1951
0
    }
1952
1953
    // Insert new records for the axis
1954
0
    for (int i = 0; i < static_cast<int>(axisList.size()); ++i) {
1955
0
        const auto &axis = axisList[i];
1956
0
        std::string uomAuthName;
1957
0
        std::string uomCode;
1958
0
        identifyOrInsert(dbContext, axis->unit(), ownerAuthName, uomAuthName,
1959
0
                         uomCode, sqlStatements);
1960
0
        const auto sql = formatStatement(
1961
0
            "INSERT INTO axis VALUES("
1962
0
            "'%q','%q','%q','%q','%q','%q','%q',%d,'%q','%q');",
1963
0
            authName.c_str(), (code + "_AXIS_" + toString(i + 1)).c_str(),
1964
0
            axis->nameStr().c_str(), axis->abbreviation().c_str(),
1965
0
            axis->direction().toString().c_str(), authName.c_str(),
1966
0
            code.c_str(), i + 1, uomAuthName.c_str(), uomCode.c_str());
1967
0
        appendSql(sqlStatements, sql);
1968
0
    }
1969
0
}
1970
1971
// ---------------------------------------------------------------------------
1972
1973
static void
1974
addAllowedAuthoritiesCond(const std::vector<std::string> &allowedAuthorities,
1975
                          const std::string &authName, std::string &sql,
1976
0
                          ListOfParams &params) {
1977
0
    sql += "auth_name IN (?";
1978
0
    params.emplace_back(authName);
1979
0
    for (const auto &allowedAuthority : allowedAuthorities) {
1980
0
        sql += ",?";
1981
0
        params.emplace_back(allowedAuthority);
1982
0
    }
1983
0
    sql += ')';
1984
0
}
1985
1986
// ---------------------------------------------------------------------------
1987
1988
void DatabaseContext::Private::identifyOrInsertUsages(
1989
    const common::ObjectUsageNNPtr &obj, const std::string &tableName,
1990
    const std::string &authName, const std::string &code,
1991
    const std::vector<std::string> &allowedAuthorities,
1992
0
    std::vector<std::string> &sqlStatements) {
1993
1994
0
    std::string usageCode("USAGE_");
1995
0
    const std::string upperTableName(toupper(tableName));
1996
0
    if (!starts_with(code, upperTableName)) {
1997
0
        usageCode += upperTableName;
1998
0
        usageCode += '_';
1999
0
    }
2000
0
    usageCode += code;
2001
2002
0
    const auto &domains = obj->domains();
2003
0
    if (domains.empty()) {
2004
0
        const auto sql =
2005
0
            formatStatement("INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2006
0
                            "'PROJ','EXTENT_UNKNOWN','PROJ','SCOPE_UNKNOWN');",
2007
0
                            authName.c_str(), usageCode.c_str(),
2008
0
                            tableName.c_str(), authName.c_str(), code.c_str());
2009
0
        appendSql(sqlStatements, sql);
2010
0
        return;
2011
0
    }
2012
2013
0
    int usageCounter = 1;
2014
0
    for (const auto &domain : domains) {
2015
0
        std::string scopeAuthName;
2016
0
        std::string scopeCode;
2017
0
        const auto &scope = domain->scope();
2018
0
        if (scope.has_value()) {
2019
0
            std::string sql =
2020
0
                "SELECT auth_name, code, "
2021
0
                "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2022
0
                "AS order_idx "
2023
0
                "FROM scope WHERE scope = ? AND deprecated = 0 AND ";
2024
0
            ListOfParams params{*scope};
2025
0
            addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2026
0
                                      params);
2027
0
            sql += " ORDER BY order_idx, auth_name, code";
2028
0
            const auto rows = run(sql, params);
2029
0
            if (!rows.empty()) {
2030
0
                const auto &row = rows.front();
2031
0
                scopeAuthName = row[0];
2032
0
                scopeCode = row[1];
2033
0
            } else {
2034
0
                scopeAuthName = authName;
2035
0
                scopeCode = "SCOPE_";
2036
0
                scopeCode += tableName;
2037
0
                scopeCode += '_';
2038
0
                scopeCode += code;
2039
0
                const auto sqlToInsert = formatStatement(
2040
0
                    "INSERT INTO scope VALUES('%q','%q','%q',0);",
2041
0
                    scopeAuthName.c_str(), scopeCode.c_str(), scope->c_str());
2042
0
                appendSql(sqlStatements, sqlToInsert);
2043
0
            }
2044
0
        } else {
2045
0
            scopeAuthName = "PROJ";
2046
0
            scopeCode = "SCOPE_UNKNOWN";
2047
0
        }
2048
2049
0
        std::string extentAuthName("PROJ");
2050
0
        std::string extentCode("EXTENT_UNKNOWN");
2051
0
        const auto &extent = domain->domainOfValidity();
2052
0
        if (extent) {
2053
0
            const auto &geogElts = extent->geographicElements();
2054
0
            if (!geogElts.empty()) {
2055
0
                const auto bbox =
2056
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
2057
0
                        geogElts.front().get());
2058
0
                if (bbox) {
2059
0
                    std::string sql =
2060
0
                        "SELECT auth_name, code, "
2061
0
                        "(CASE WHEN auth_name = 'EPSG' THEN 0 ELSE 1 END) "
2062
0
                        "AS order_idx "
2063
0
                        "FROM extent WHERE south_lat = ? AND north_lat = ? "
2064
0
                        "AND west_lon = ? AND east_lon = ? AND deprecated = 0 "
2065
0
                        "AND ";
2066
0
                    ListOfParams params{
2067
0
                        bbox->southBoundLatitude(), bbox->northBoundLatitude(),
2068
0
                        bbox->westBoundLongitude(), bbox->eastBoundLongitude()};
2069
0
                    addAllowedAuthoritiesCond(allowedAuthorities, authName, sql,
2070
0
                                              params);
2071
0
                    sql += " ORDER BY order_idx, auth_name, code";
2072
0
                    const auto rows = run(sql, params);
2073
0
                    if (!rows.empty()) {
2074
0
                        const auto &row = rows.front();
2075
0
                        extentAuthName = row[0];
2076
0
                        extentCode = row[1];
2077
0
                    } else {
2078
0
                        extentAuthName = authName;
2079
0
                        extentCode = "EXTENT_";
2080
0
                        extentCode += tableName;
2081
0
                        extentCode += '_';
2082
0
                        extentCode += code;
2083
0
                        std::string description(*(extent->description()));
2084
0
                        if (description.empty()) {
2085
0
                            description = "unknown";
2086
0
                        }
2087
0
                        const auto sqlToInsert = formatStatement(
2088
0
                            "INSERT INTO extent "
2089
0
                            "VALUES('%q','%q','%q','%q',%f,%f,%f,%f,0);",
2090
0
                            extentAuthName.c_str(), extentCode.c_str(),
2091
0
                            description.c_str(), description.c_str(),
2092
0
                            bbox->southBoundLatitude(),
2093
0
                            bbox->northBoundLatitude(),
2094
0
                            bbox->westBoundLongitude(),
2095
0
                            bbox->eastBoundLongitude());
2096
0
                        appendSql(sqlStatements, sqlToInsert);
2097
0
                    }
2098
0
                }
2099
0
            }
2100
0
        }
2101
2102
0
        if (domains.size() > 1) {
2103
0
            usageCode += '_';
2104
0
            usageCode += toString(usageCounter);
2105
0
        }
2106
0
        const auto sql = formatStatement(
2107
0
            "INSERT INTO usage VALUES('%q','%q','%q','%q','%q',"
2108
0
            "'%q','%q','%q','%q');",
2109
0
            authName.c_str(), usageCode.c_str(), tableName.c_str(),
2110
0
            authName.c_str(), code.c_str(), extentAuthName.c_str(),
2111
0
            extentCode.c_str(), scopeAuthName.c_str(), scopeCode.c_str());
2112
0
        appendSql(sqlStatements, sql);
2113
2114
0
        usageCounter++;
2115
0
    }
2116
0
}
2117
2118
// ---------------------------------------------------------------------------
2119
2120
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2121
    const datum::PrimeMeridianNNPtr &pm, const std::string &authName,
2122
    const std::string &code, bool /*numericCode*/,
2123
0
    const std::vector<std::string> &allowedAuthorities) {
2124
2125
0
    const auto self = NN_NO_CHECK(self_.lock());
2126
2127
    // Check if the object is already known under that code
2128
0
    std::string pmAuthName;
2129
0
    std::string pmCode;
2130
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pm, pmAuthName,
2131
0
                           pmCode);
2132
0
    if (pmAuthName == authName && pmCode == code) {
2133
0
        return {};
2134
0
    }
2135
2136
0
    std::vector<std::string> sqlStatements;
2137
2138
    // Insert new record in prime_meridian table
2139
0
    std::string uomAuthName;
2140
0
    std::string uomCode;
2141
0
    identifyOrInsert(self, pm->longitude().unit(), authName, uomAuthName,
2142
0
                     uomCode, sqlStatements);
2143
2144
0
    const auto sql = formatStatement(
2145
0
        "INSERT INTO prime_meridian VALUES("
2146
0
        "'%q','%q','%q',%f,'%q','%q',0);",
2147
0
        authName.c_str(), code.c_str(), pm->nameStr().c_str(),
2148
0
        pm->longitude().value(), uomAuthName.c_str(), uomCode.c_str());
2149
0
    appendSql(sqlStatements, sql);
2150
2151
0
    return sqlStatements;
2152
0
}
2153
2154
// ---------------------------------------------------------------------------
2155
2156
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2157
    const datum::EllipsoidNNPtr &ellipsoid, const std::string &authName,
2158
    const std::string &code, bool /*numericCode*/,
2159
0
    const std::vector<std::string> &allowedAuthorities) {
2160
2161
0
    const auto self = NN_NO_CHECK(self_.lock());
2162
2163
    // Check if the object is already known under that code
2164
0
    std::string ellipsoidAuthName;
2165
0
    std::string ellipsoidCode;
2166
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoid,
2167
0
                           ellipsoidAuthName, ellipsoidCode);
2168
0
    if (ellipsoidAuthName == authName && ellipsoidCode == code) {
2169
0
        return {};
2170
0
    }
2171
2172
0
    std::vector<std::string> sqlStatements;
2173
2174
    // Find or insert celestial body
2175
0
    const auto &semiMajorAxis = ellipsoid->semiMajorAxis();
2176
0
    const double semiMajorAxisMetre = semiMajorAxis.getSIValue();
2177
0
    constexpr double tolerance = 0.005;
2178
0
    std::string bodyAuthName;
2179
0
    std::string bodyCode;
2180
0
    auto res = run("SELECT auth_name, code, "
2181
0
                   "(ABS(semi_major_axis - ?) / semi_major_axis ) "
2182
0
                   "AS rel_error FROM celestial_body WHERE rel_error <= ?",
2183
0
                   {semiMajorAxisMetre, tolerance});
2184
0
    if (!res.empty()) {
2185
0
        const auto &row = res.front();
2186
0
        bodyAuthName = row[0];
2187
0
        bodyCode = row[1];
2188
0
    } else {
2189
0
        bodyAuthName = authName;
2190
0
        bodyCode = "BODY_" + code;
2191
0
        const auto bodyName = "Body of " + ellipsoid->nameStr();
2192
0
        const auto sql = formatStatement(
2193
0
            "INSERT INTO celestial_body VALUES('%q','%q','%q',%f);",
2194
0
            bodyAuthName.c_str(), bodyCode.c_str(), bodyName.c_str(),
2195
0
            semiMajorAxisMetre);
2196
0
        appendSql(sqlStatements, sql);
2197
0
    }
2198
2199
    // Insert new record in ellipsoid table
2200
0
    std::string uomAuthName;
2201
0
    std::string uomCode;
2202
0
    identifyOrInsert(self, semiMajorAxis.unit(), authName, uomAuthName, uomCode,
2203
0
                     sqlStatements);
2204
0
    std::string invFlattening("NULL");
2205
0
    std::string semiMinorAxis("NULL");
2206
0
    if (ellipsoid->isSphere() || ellipsoid->semiMinorAxis().has_value()) {
2207
0
        semiMinorAxis = toString(ellipsoid->computeSemiMinorAxis().value());
2208
0
    } else {
2209
0
        invFlattening = toString(ellipsoid->computedInverseFlattening());
2210
0
    }
2211
2212
0
    const auto sql = formatStatement(
2213
0
        "INSERT INTO ellipsoid VALUES("
2214
0
        "'%q','%q','%q','%q','%q','%q',%f,'%q','%q',%s,%s,0);",
2215
0
        authName.c_str(), code.c_str(), ellipsoid->nameStr().c_str(),
2216
0
        "", // description
2217
0
        bodyAuthName.c_str(), bodyCode.c_str(), semiMajorAxis.value(),
2218
0
        uomAuthName.c_str(), uomCode.c_str(), invFlattening.c_str(),
2219
0
        semiMinorAxis.c_str());
2220
0
    appendSql(sqlStatements, sql);
2221
2222
0
    return sqlStatements;
2223
0
}
2224
2225
// ---------------------------------------------------------------------------
2226
2227
0
static std::string anchorEpochToStr(double val) {
2228
0
    constexpr int BUF_SIZE = 16;
2229
0
    char szBuffer[BUF_SIZE];
2230
0
    sqlite3_snprintf(BUF_SIZE, szBuffer, "%.3f", val);
2231
0
    return szBuffer;
2232
0
}
2233
2234
// ---------------------------------------------------------------------------
2235
2236
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2237
    const datum::GeodeticReferenceFrameNNPtr &datum,
2238
    const std::string &authName, const std::string &code, bool numericCode,
2239
0
    const std::vector<std::string> &allowedAuthorities) {
2240
2241
0
    const auto self = NN_NO_CHECK(self_.lock());
2242
2243
    // Check if the object is already known under that code
2244
0
    std::string datumAuthName;
2245
0
    std::string datumCode;
2246
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2247
0
                           datumAuthName, datumCode);
2248
0
    if (datumAuthName == authName && datumCode == code) {
2249
0
        return {};
2250
0
    }
2251
2252
0
    std::vector<std::string> sqlStatements;
2253
2254
    // Find or insert ellipsoid
2255
0
    std::string ellipsoidAuthName;
2256
0
    std::string ellipsoidCode;
2257
0
    const auto &ellipsoidOfDatum = datum->ellipsoid();
2258
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ellipsoidOfDatum,
2259
0
                           ellipsoidAuthName, ellipsoidCode);
2260
0
    if (ellipsoidAuthName.empty()) {
2261
0
        ellipsoidAuthName = authName;
2262
0
        if (numericCode) {
2263
0
            ellipsoidCode = self->suggestsCodeFor(ellipsoidOfDatum,
2264
0
                                                  ellipsoidAuthName, true);
2265
0
        } else {
2266
0
            ellipsoidCode = "ELLPS_" + code;
2267
0
        }
2268
0
        sqlStatements = self->getInsertStatementsFor(
2269
0
            ellipsoidOfDatum, ellipsoidAuthName, ellipsoidCode, numericCode,
2270
0
            allowedAuthorities);
2271
0
    }
2272
2273
    // Find or insert prime meridian
2274
0
    std::string pmAuthName;
2275
0
    std::string pmCode;
2276
0
    const auto &pmOfDatum = datum->primeMeridian();
2277
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, pmOfDatum,
2278
0
                           pmAuthName, pmCode);
2279
0
    if (pmAuthName.empty()) {
2280
0
        pmAuthName = authName;
2281
0
        if (numericCode) {
2282
0
            pmCode = self->suggestsCodeFor(pmOfDatum, pmAuthName, true);
2283
0
        } else {
2284
0
            pmCode = "PM_" + code;
2285
0
        }
2286
0
        const auto sqlStatementsTmp = self->getInsertStatementsFor(
2287
0
            pmOfDatum, pmAuthName, pmCode, numericCode, allowedAuthorities);
2288
0
        sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2289
0
                             sqlStatementsTmp.end());
2290
0
    }
2291
2292
    // Insert new record in geodetic_datum table
2293
0
    std::string publicationDate("NULL");
2294
0
    if (datum->publicationDate().has_value()) {
2295
0
        publicationDate = '\'';
2296
0
        publicationDate +=
2297
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2298
0
        publicationDate += '\'';
2299
0
    }
2300
0
    std::string frameReferenceEpoch("NULL");
2301
0
    const auto dynamicDatum =
2302
0
        dynamic_cast<const datum::DynamicGeodeticReferenceFrame *>(datum.get());
2303
0
    if (dynamicDatum) {
2304
0
        frameReferenceEpoch =
2305
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2306
0
    }
2307
0
    const std::string anchor(*(datum->anchorDefinition()));
2308
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2309
0
    const auto sql = formatStatement(
2310
0
        "INSERT INTO geodetic_datum VALUES("
2311
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2312
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2313
0
        "", // description
2314
0
        ellipsoidAuthName.c_str(), ellipsoidCode.c_str(), pmAuthName.c_str(),
2315
0
        pmCode.c_str(), publicationDate.c_str(), frameReferenceEpoch.c_str(),
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
2324
0
    identifyOrInsertUsages(datum, "geodetic_datum", authName, code,
2325
0
                           allowedAuthorities, sqlStatements);
2326
2327
0
    return sqlStatements;
2328
0
}
2329
2330
// ---------------------------------------------------------------------------
2331
2332
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2333
    const datum::DatumEnsembleNNPtr &ensemble, const std::string &authName,
2334
    const std::string &code, bool numericCode,
2335
0
    const std::vector<std::string> &allowedAuthorities) {
2336
0
    const auto self = NN_NO_CHECK(self_.lock());
2337
2338
    // Check if the object is already known under that code
2339
0
    std::string datumAuthName;
2340
0
    std::string datumCode;
2341
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, ensemble,
2342
0
                           datumAuthName, datumCode);
2343
0
    if (datumAuthName == authName && datumCode == code) {
2344
0
        return {};
2345
0
    }
2346
2347
0
    std::vector<std::string> sqlStatements;
2348
2349
0
    const auto &members = ensemble->datums();
2350
0
    assert(!members.empty());
2351
2352
0
    int counter = 1;
2353
0
    std::vector<std::pair<std::string, std::string>> membersId;
2354
0
    for (const auto &member : members) {
2355
0
        std::string memberAuthName;
2356
0
        std::string memberCode;
2357
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, member,
2358
0
                               memberAuthName, memberCode);
2359
0
        if (memberAuthName.empty()) {
2360
0
            memberAuthName = authName;
2361
0
            if (numericCode) {
2362
0
                memberCode =
2363
0
                    self->suggestsCodeFor(member, memberAuthName, true);
2364
0
            } else {
2365
0
                memberCode = "MEMBER_" + toString(counter) + "_OF_" + code;
2366
0
            }
2367
0
            const auto sqlStatementsTmp =
2368
0
                self->getInsertStatementsFor(member, memberAuthName, memberCode,
2369
0
                                             numericCode, allowedAuthorities);
2370
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2371
0
                                 sqlStatementsTmp.end());
2372
0
        }
2373
2374
0
        membersId.emplace_back(
2375
0
            std::pair<std::string, std::string>(memberAuthName, memberCode));
2376
2377
0
        ++counter;
2378
0
    }
2379
2380
0
    const bool isGeodetic =
2381
0
        util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
2382
0
            members.front()) != nullptr;
2383
2384
    // Insert new record in geodetic_datum/vertical_datum table
2385
0
    const double accuracy =
2386
0
        c_locale_stod(ensemble->positionalAccuracy()->value());
2387
0
    if (isGeodetic) {
2388
0
        const auto firstDatum =
2389
0
            AuthorityFactory::create(self, membersId.front().first)
2390
0
                ->createGeodeticDatum(membersId.front().second);
2391
0
        const auto &ellipsoid = firstDatum->ellipsoid();
2392
0
        const auto &ellipsoidIds = ellipsoid->identifiers();
2393
0
        assert(!ellipsoidIds.empty());
2394
0
        const std::string &ellipsoidAuthName =
2395
0
            *(ellipsoidIds.front()->codeSpace());
2396
0
        const std::string &ellipsoidCode = ellipsoidIds.front()->code();
2397
0
        const auto &pm = firstDatum->primeMeridian();
2398
0
        const auto &pmIds = pm->identifiers();
2399
0
        assert(!pmIds.empty());
2400
0
        const std::string &pmAuthName = *(pmIds.front()->codeSpace());
2401
0
        const std::string &pmCode = pmIds.front()->code();
2402
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2403
0
        const util::optional<common::Measure> &anchorEpoch =
2404
0
            firstDatum->anchorEpoch();
2405
0
        const auto sql = formatStatement(
2406
0
            "INSERT INTO geodetic_datum VALUES("
2407
0
            "'%q','%q','%q','%q','%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2408
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2409
0
            "", // description
2410
0
            ellipsoidAuthName.c_str(), ellipsoidCode.c_str(),
2411
0
            pmAuthName.c_str(), pmCode.c_str(), accuracy,
2412
0
            anchor.empty() ? nullptr : anchor.c_str(),
2413
0
            anchorEpoch.has_value()
2414
0
                ? anchorEpochToStr(
2415
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2416
0
                      .c_str()
2417
0
                : "NULL");
2418
0
        appendSql(sqlStatements, sql);
2419
0
    } else {
2420
0
        const auto firstDatum =
2421
0
            AuthorityFactory::create(self, membersId.front().first)
2422
0
                ->createVerticalDatum(membersId.front().second);
2423
0
        const std::string anchor(*(firstDatum->anchorDefinition()));
2424
0
        const util::optional<common::Measure> &anchorEpoch =
2425
0
            firstDatum->anchorEpoch();
2426
0
        const auto sql = formatStatement(
2427
0
            "INSERT INTO vertical_datum VALUES("
2428
0
            "'%q','%q','%q','%q',NULL,NULL,%f,%Q,%s,0);",
2429
0
            authName.c_str(), code.c_str(), ensemble->nameStr().c_str(),
2430
0
            "", // description
2431
0
            accuracy, anchor.empty() ? nullptr : anchor.c_str(),
2432
0
            anchorEpoch.has_value()
2433
0
                ? anchorEpochToStr(
2434
0
                      anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2435
0
                      .c_str()
2436
0
                : "NULL");
2437
0
        appendSql(sqlStatements, sql);
2438
0
    }
2439
0
    identifyOrInsertUsages(ensemble,
2440
0
                           isGeodetic ? "geodetic_datum" : "vertical_datum",
2441
0
                           authName, code, allowedAuthorities, sqlStatements);
2442
2443
0
    const char *tableName = isGeodetic ? "geodetic_datum_ensemble_member"
2444
0
                                       : "vertical_datum_ensemble_member";
2445
0
    counter = 1;
2446
0
    for (const auto &authCodePair : membersId) {
2447
0
        const auto sql = formatStatement(
2448
0
            "INSERT INTO %s VALUES("
2449
0
            "'%q','%q','%q','%q',%d);",
2450
0
            tableName, authName.c_str(), code.c_str(),
2451
0
            authCodePair.first.c_str(), authCodePair.second.c_str(), counter);
2452
0
        appendSql(sqlStatements, sql);
2453
0
        ++counter;
2454
0
    }
2455
2456
0
    return sqlStatements;
2457
0
}
2458
2459
// ---------------------------------------------------------------------------
2460
2461
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2462
    const crs::GeodeticCRSNNPtr &crs, const std::string &authName,
2463
    const std::string &code, bool numericCode,
2464
0
    const std::vector<std::string> &allowedAuthorities) {
2465
2466
0
    const auto self = NN_NO_CHECK(self_.lock());
2467
2468
0
    std::vector<std::string> sqlStatements;
2469
2470
    // Find or insert datum/datum ensemble
2471
0
    std::string datumAuthName;
2472
0
    std::string datumCode;
2473
0
    const auto &ensemble = crs->datumEnsemble();
2474
0
    if (ensemble) {
2475
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2476
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2477
0
                               datumAuthName, datumCode);
2478
0
        if (datumAuthName.empty()) {
2479
0
            datumAuthName = authName;
2480
0
            if (numericCode) {
2481
0
                datumCode =
2482
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2483
0
            } else {
2484
0
                datumCode = "GEODETIC_DATUM_" + code;
2485
0
            }
2486
0
            sqlStatements = self->getInsertStatementsFor(
2487
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2488
0
                allowedAuthorities);
2489
0
        }
2490
0
    } else {
2491
0
        const auto &datum = crs->datum();
2492
0
        assert(datum);
2493
0
        const auto datumNN = NN_NO_CHECK(datum);
2494
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2495
0
                               datumAuthName, datumCode);
2496
0
        if (datumAuthName.empty()) {
2497
0
            datumAuthName = authName;
2498
0
            if (numericCode) {
2499
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2500
0
            } else {
2501
0
                datumCode = "GEODETIC_DATUM_" + code;
2502
0
            }
2503
0
            sqlStatements =
2504
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2505
0
                                             numericCode, allowedAuthorities);
2506
0
        }
2507
0
    }
2508
2509
    // Find or insert coordinate system
2510
0
    const auto &coordinateSystem = crs->coordinateSystem();
2511
0
    std::string csAuthName;
2512
0
    std::string csCode;
2513
0
    identifyOrInsert(self, coordinateSystem, "GEODETIC_CRS", authName, code,
2514
0
                     csAuthName, csCode, sqlStatements);
2515
2516
0
    const char *type = GEOG_2D;
2517
0
    if (coordinateSystem->axisList().size() == 3) {
2518
0
        if (dynamic_cast<const crs::GeographicCRS *>(crs.get())) {
2519
0
            type = GEOG_3D;
2520
0
        } else {
2521
0
            type = GEOCENTRIC;
2522
0
        }
2523
0
    }
2524
2525
    // Insert new record in geodetic_crs table
2526
0
    const auto sql =
2527
0
        formatStatement("INSERT INTO geodetic_crs VALUES("
2528
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2529
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2530
0
                        "", // description
2531
0
                        type, csAuthName.c_str(), csCode.c_str(),
2532
0
                        datumAuthName.c_str(), datumCode.c_str());
2533
0
    appendSql(sqlStatements, sql);
2534
2535
0
    identifyOrInsertUsages(crs, "geodetic_crs", authName, code,
2536
0
                           allowedAuthorities, sqlStatements);
2537
0
    return sqlStatements;
2538
0
}
2539
2540
// ---------------------------------------------------------------------------
2541
2542
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2543
    const crs::ProjectedCRSNNPtr &crs, const std::string &authName,
2544
    const std::string &code, bool numericCode,
2545
0
    const std::vector<std::string> &allowedAuthorities) {
2546
2547
0
    const auto self = NN_NO_CHECK(self_.lock());
2548
2549
0
    std::vector<std::string> sqlStatements;
2550
2551
    // Find or insert base geodetic CRS
2552
0
    const auto &baseCRS = crs->baseCRS();
2553
0
    std::string geodAuthName;
2554
0
    std::string geodCode;
2555
2556
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2557
0
    allowedAuthoritiesTmp.emplace_back(authName);
2558
0
    for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2559
0
        const auto factory = AuthorityFactory::create(self, allowedAuthority);
2560
0
        const auto candidates = baseCRS->identify(factory);
2561
0
        for (const auto &candidate : candidates) {
2562
0
            if (candidate.second == 100) {
2563
0
                const auto &ids = candidate.first->identifiers();
2564
0
                if (!ids.empty()) {
2565
0
                    const auto &id = ids.front();
2566
0
                    geodAuthName = *(id->codeSpace());
2567
0
                    geodCode = id->code();
2568
0
                    break;
2569
0
                }
2570
0
            }
2571
0
            if (!geodAuthName.empty()) {
2572
0
                break;
2573
0
            }
2574
0
        }
2575
0
    }
2576
0
    if (geodAuthName.empty()) {
2577
0
        geodAuthName = authName;
2578
0
        geodCode = "GEODETIC_CRS_" + code;
2579
0
        sqlStatements = self->getInsertStatementsFor(
2580
0
            baseCRS, geodAuthName, geodCode, numericCode, allowedAuthorities);
2581
0
    }
2582
2583
    // Insert new record in conversion table
2584
0
    const auto &conversion = crs->derivingConversionRef();
2585
0
    std::string convAuthName(authName);
2586
0
    std::string convCode("CONVERSION_" + code);
2587
0
    if (numericCode) {
2588
0
        convCode = self->suggestsCodeFor(conversion, convAuthName, true);
2589
0
    }
2590
0
    {
2591
0
        const auto &method = conversion->method();
2592
0
        const auto &methodIds = method->identifiers();
2593
0
        std::string methodAuthName;
2594
0
        std::string methodCode;
2595
0
        const operation::MethodMapping *methodMapping = nullptr;
2596
0
        if (methodIds.empty()) {
2597
0
            const int epsgCode = method->getEPSGCode();
2598
0
            if (epsgCode > 0) {
2599
0
                methodAuthName = metadata::Identifier::EPSG;
2600
0
                methodCode = toString(epsgCode);
2601
0
            } else {
2602
0
                const auto &methodName = method->nameStr();
2603
0
                size_t nProjectionMethodMappings = 0;
2604
0
                const auto projectionMethodMappings =
2605
0
                    operation::getProjectionMethodMappings(
2606
0
                        nProjectionMethodMappings);
2607
0
                for (size_t i = 0; i < nProjectionMethodMappings; ++i) {
2608
0
                    const auto &mapping = projectionMethodMappings[i];
2609
0
                    if (metadata::Identifier::isEquivalentName(
2610
0
                            mapping.wkt2_name, methodName.c_str())) {
2611
0
                        methodMapping = &mapping;
2612
0
                    }
2613
0
                }
2614
0
                if (methodMapping == nullptr ||
2615
0
                    methodMapping->proj_name_main == nullptr) {
2616
0
                    throw FactoryException("Cannot insert projection with "
2617
0
                                           "method without identifier");
2618
0
                }
2619
0
                methodAuthName = "PROJ";
2620
0
                methodCode = methodMapping->proj_name_main;
2621
0
                if (methodMapping->proj_name_aux) {
2622
0
                    methodCode += ' ';
2623
0
                    methodCode += methodMapping->proj_name_aux;
2624
0
                }
2625
0
            }
2626
0
        } else {
2627
0
            const auto &methodId = methodIds.front();
2628
0
            methodAuthName = *(methodId->codeSpace());
2629
0
            methodCode = methodId->code();
2630
0
        }
2631
2632
0
        auto sql = formatStatement("INSERT INTO conversion VALUES("
2633
0
                                   "'%q','%q','%q','','%q','%q','%q'",
2634
0
                                   convAuthName.c_str(), convCode.c_str(),
2635
0
                                   conversion->nameStr().c_str(),
2636
0
                                   methodAuthName.c_str(), methodCode.c_str(),
2637
0
                                   method->nameStr().c_str());
2638
0
        const auto &srcValues = conversion->parameterValues();
2639
0
        if (srcValues.size() > N_MAX_PARAMS) {
2640
0
            throw FactoryException("Cannot insert projection with more than " +
2641
0
                                   toString(static_cast<int>(N_MAX_PARAMS)) +
2642
0
                                   " parameters");
2643
0
        }
2644
2645
0
        std::vector<operation::GeneralParameterValueNNPtr> values;
2646
0
        if (methodMapping == nullptr) {
2647
0
            if (methodAuthName == metadata::Identifier::EPSG) {
2648
0
                methodMapping = operation::getMapping(atoi(methodCode.c_str()));
2649
0
            } else {
2650
0
                methodMapping =
2651
0
                    operation::getMapping(method->nameStr().c_str());
2652
0
            }
2653
0
        }
2654
0
        if (methodMapping != nullptr) {
2655
            // Re-order projection parameters in their reference order
2656
0
            for (size_t j = 0; methodMapping->params[j] != nullptr; ++j) {
2657
0
                for (size_t i = 0; i < srcValues.size(); ++i) {
2658
0
                    auto opParamValue = dynamic_cast<
2659
0
                        const operation::OperationParameterValue *>(
2660
0
                        srcValues[i].get());
2661
0
                    if (!opParamValue) {
2662
0
                        throw FactoryException("Cannot insert projection with "
2663
0
                                               "non-OperationParameterValue");
2664
0
                    }
2665
0
                    if (methodMapping->params[j]->wkt2_name &&
2666
0
                        opParamValue->parameter()->nameStr() ==
2667
0
                            methodMapping->params[j]->wkt2_name) {
2668
0
                        values.emplace_back(srcValues[i]);
2669
0
                    }
2670
0
                }
2671
0
            }
2672
0
        }
2673
0
        if (values.size() != srcValues.size()) {
2674
0
            values = srcValues;
2675
0
        }
2676
2677
0
        for (const auto &genOpParamvalue : values) {
2678
0
            auto opParamValue =
2679
0
                dynamic_cast<const operation::OperationParameterValue *>(
2680
0
                    genOpParamvalue.get());
2681
0
            if (!opParamValue) {
2682
0
                throw FactoryException("Cannot insert projection with "
2683
0
                                       "non-OperationParameterValue");
2684
0
            }
2685
0
            const auto &param = opParamValue->parameter();
2686
0
            const auto &paramIds = param->identifiers();
2687
0
            std::string paramAuthName;
2688
0
            std::string paramCode;
2689
0
            if (paramIds.empty()) {
2690
0
                const int paramEPSGCode = param->getEPSGCode();
2691
0
                if (paramEPSGCode == 0) {
2692
0
                    throw FactoryException(
2693
0
                        "Cannot insert projection with method parameter "
2694
0
                        "without identifier");
2695
0
                }
2696
0
                paramAuthName = metadata::Identifier::EPSG;
2697
0
                paramCode = toString(paramEPSGCode);
2698
0
            } else {
2699
0
                const auto &paramId = paramIds.front();
2700
0
                paramAuthName = *(paramId->codeSpace());
2701
0
                paramCode = paramId->code();
2702
0
            }
2703
0
            const auto &value = opParamValue->parameterValue()->value();
2704
0
            const auto &unit = value.unit();
2705
0
            std::string uomAuthName;
2706
0
            std::string uomCode;
2707
0
            identifyOrInsert(self, unit, authName, uomAuthName, uomCode,
2708
0
                             sqlStatements);
2709
0
            sql += formatStatement(",'%q','%q','%q',%f,'%q','%q'",
2710
0
                                   paramAuthName.c_str(), paramCode.c_str(),
2711
0
                                   param->nameStr().c_str(), value.value(),
2712
0
                                   uomAuthName.c_str(), uomCode.c_str());
2713
0
        }
2714
0
        for (size_t i = values.size(); i < N_MAX_PARAMS; ++i) {
2715
0
            sql += ",NULL,NULL,NULL,NULL,NULL,NULL";
2716
0
        }
2717
0
        sql += ",0);";
2718
0
        appendSql(sqlStatements, sql);
2719
0
        identifyOrInsertUsages(crs, "conversion", convAuthName, convCode,
2720
0
                               allowedAuthorities, sqlStatements);
2721
0
    }
2722
2723
    // Find or insert coordinate system
2724
0
    const auto &coordinateSystem = crs->coordinateSystem();
2725
0
    std::string csAuthName;
2726
0
    std::string csCode;
2727
0
    identifyOrInsert(self, coordinateSystem, "PROJECTED_CRS", authName, code,
2728
0
                     csAuthName, csCode, sqlStatements);
2729
2730
    // Insert new record in projected_crs table
2731
0
    const auto sql = formatStatement(
2732
0
        "INSERT INTO projected_crs VALUES("
2733
0
        "'%q','%q','%q','%q','%q','%q','%q','%q','%q','%q',NULL,0);",
2734
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2735
0
        "", // description
2736
0
        csAuthName.c_str(), csCode.c_str(), geodAuthName.c_str(),
2737
0
        geodCode.c_str(), convAuthName.c_str(), convCode.c_str());
2738
0
    appendSql(sqlStatements, sql);
2739
2740
0
    identifyOrInsertUsages(crs, "projected_crs", authName, code,
2741
0
                           allowedAuthorities, sqlStatements);
2742
2743
0
    return sqlStatements;
2744
0
}
2745
2746
// ---------------------------------------------------------------------------
2747
2748
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2749
    const datum::VerticalReferenceFrameNNPtr &datum,
2750
    const std::string &authName, const std::string &code,
2751
    bool /* numericCode */,
2752
0
    const std::vector<std::string> &allowedAuthorities) {
2753
2754
0
    const auto self = NN_NO_CHECK(self_.lock());
2755
2756
0
    std::vector<std::string> sqlStatements;
2757
2758
    // Check if the object is already known under that code
2759
0
    std::string datumAuthName;
2760
0
    std::string datumCode;
2761
0
    identifyFromNameOrCode(self, allowedAuthorities, authName, datum,
2762
0
                           datumAuthName, datumCode);
2763
0
    if (datumAuthName == authName && datumCode == code) {
2764
0
        return {};
2765
0
    }
2766
2767
    // Insert new record in vertical_datum table
2768
0
    std::string publicationDate("NULL");
2769
0
    if (datum->publicationDate().has_value()) {
2770
0
        publicationDate = '\'';
2771
0
        publicationDate +=
2772
0
            replaceAll(datum->publicationDate()->toString(), "'", "''");
2773
0
        publicationDate += '\'';
2774
0
    }
2775
0
    std::string frameReferenceEpoch("NULL");
2776
0
    const auto dynamicDatum =
2777
0
        dynamic_cast<const datum::DynamicVerticalReferenceFrame *>(datum.get());
2778
0
    if (dynamicDatum) {
2779
0
        frameReferenceEpoch =
2780
0
            toString(dynamicDatum->frameReferenceEpoch().value());
2781
0
    }
2782
0
    const std::string anchor(*(datum->anchorDefinition()));
2783
0
    const util::optional<common::Measure> &anchorEpoch = datum->anchorEpoch();
2784
0
    const auto sql = formatStatement(
2785
0
        "INSERT INTO vertical_datum VALUES("
2786
0
        "'%q','%q','%q','%q',%s,%s,NULL,%Q,%s,0);",
2787
0
        authName.c_str(), code.c_str(), datum->nameStr().c_str(),
2788
0
        "", // description
2789
0
        publicationDate.c_str(), frameReferenceEpoch.c_str(),
2790
0
        anchor.empty() ? nullptr : anchor.c_str(),
2791
0
        anchorEpoch.has_value()
2792
0
            ? anchorEpochToStr(
2793
0
                  anchorEpoch->convertToUnit(common::UnitOfMeasure::YEAR))
2794
0
                  .c_str()
2795
0
            : "NULL");
2796
0
    appendSql(sqlStatements, sql);
2797
2798
0
    identifyOrInsertUsages(datum, "vertical_datum", authName, code,
2799
0
                           allowedAuthorities, sqlStatements);
2800
2801
0
    return sqlStatements;
2802
0
}
2803
2804
// ---------------------------------------------------------------------------
2805
2806
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2807
    const crs::VerticalCRSNNPtr &crs, const std::string &authName,
2808
    const std::string &code, bool numericCode,
2809
0
    const std::vector<std::string> &allowedAuthorities) {
2810
2811
0
    const auto self = NN_NO_CHECK(self_.lock());
2812
2813
0
    std::vector<std::string> sqlStatements;
2814
2815
    // Find or insert datum/datum ensemble
2816
0
    std::string datumAuthName;
2817
0
    std::string datumCode;
2818
0
    const auto &ensemble = crs->datumEnsemble();
2819
0
    if (ensemble) {
2820
0
        const auto ensembleNN = NN_NO_CHECK(ensemble);
2821
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, ensembleNN,
2822
0
                               datumAuthName, datumCode);
2823
0
        if (datumAuthName.empty()) {
2824
0
            datumAuthName = authName;
2825
0
            if (numericCode) {
2826
0
                datumCode =
2827
0
                    self->suggestsCodeFor(ensembleNN, datumAuthName, true);
2828
0
            } else {
2829
0
                datumCode = "VERTICAL_DATUM_" + code;
2830
0
            }
2831
0
            sqlStatements = self->getInsertStatementsFor(
2832
0
                ensembleNN, datumAuthName, datumCode, numericCode,
2833
0
                allowedAuthorities);
2834
0
        }
2835
0
    } else {
2836
0
        const auto &datum = crs->datum();
2837
0
        assert(datum);
2838
0
        const auto datumNN = NN_NO_CHECK(datum);
2839
0
        identifyFromNameOrCode(self, allowedAuthorities, authName, datumNN,
2840
0
                               datumAuthName, datumCode);
2841
0
        if (datumAuthName.empty()) {
2842
0
            datumAuthName = authName;
2843
0
            if (numericCode) {
2844
0
                datumCode = self->suggestsCodeFor(datumNN, datumAuthName, true);
2845
0
            } else {
2846
0
                datumCode = "VERTICAL_DATUM_" + code;
2847
0
            }
2848
0
            sqlStatements =
2849
0
                self->getInsertStatementsFor(datumNN, datumAuthName, datumCode,
2850
0
                                             numericCode, allowedAuthorities);
2851
0
        }
2852
0
    }
2853
2854
    // Find or insert coordinate system
2855
0
    const auto &coordinateSystem = crs->coordinateSystem();
2856
0
    std::string csAuthName;
2857
0
    std::string csCode;
2858
0
    identifyOrInsert(self, coordinateSystem, "VERTICAL_CRS", authName, code,
2859
0
                     csAuthName, csCode, sqlStatements);
2860
2861
    // Insert new record in vertical_crs table
2862
0
    const auto sql =
2863
0
        formatStatement("INSERT INTO vertical_crs VALUES("
2864
0
                        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2865
0
                        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2866
0
                        "", // description
2867
0
                        csAuthName.c_str(), csCode.c_str(),
2868
0
                        datumAuthName.c_str(), datumCode.c_str());
2869
0
    appendSql(sqlStatements, sql);
2870
2871
0
    identifyOrInsertUsages(crs, "vertical_crs", authName, code,
2872
0
                           allowedAuthorities, sqlStatements);
2873
2874
0
    return sqlStatements;
2875
0
}
2876
2877
// ---------------------------------------------------------------------------
2878
2879
std::vector<std::string> DatabaseContext::Private::getInsertStatementsFor(
2880
    const crs::CompoundCRSNNPtr &crs, const std::string &authName,
2881
    const std::string &code, bool numericCode,
2882
0
    const std::vector<std::string> &allowedAuthorities) {
2883
2884
0
    const auto self = NN_NO_CHECK(self_.lock());
2885
2886
0
    std::vector<std::string> sqlStatements;
2887
2888
0
    int counter = 1;
2889
0
    std::vector<std::pair<std::string, std::string>> componentsId;
2890
0
    const auto &components = crs->componentReferenceSystems();
2891
0
    if (components.size() != 2) {
2892
0
        throw FactoryException(
2893
0
            "Cannot insert compound CRS with number of components != 2");
2894
0
    }
2895
2896
0
    auto allowedAuthoritiesTmp(allowedAuthorities);
2897
0
    allowedAuthoritiesTmp.emplace_back(authName);
2898
2899
0
    for (const auto &component : components) {
2900
0
        std::string compAuthName;
2901
0
        std::string compCode;
2902
2903
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
2904
0
            const auto factory =
2905
0
                AuthorityFactory::create(self, allowedAuthority);
2906
0
            const auto candidates = component->identify(factory);
2907
0
            for (const auto &candidate : candidates) {
2908
0
                if (candidate.second == 100) {
2909
0
                    const auto &ids = candidate.first->identifiers();
2910
0
                    if (!ids.empty()) {
2911
0
                        const auto &id = ids.front();
2912
0
                        compAuthName = *(id->codeSpace());
2913
0
                        compCode = id->code();
2914
0
                        break;
2915
0
                    }
2916
0
                }
2917
0
                if (!compAuthName.empty()) {
2918
0
                    break;
2919
0
                }
2920
0
            }
2921
0
        }
2922
2923
0
        if (compAuthName.empty()) {
2924
0
            compAuthName = authName;
2925
0
            if (numericCode) {
2926
0
                compCode = self->suggestsCodeFor(component, compAuthName, true);
2927
0
            } else {
2928
0
                compCode = "COMPONENT_" + code + '_' + toString(counter);
2929
0
            }
2930
0
            const auto sqlStatementsTmp =
2931
0
                self->getInsertStatementsFor(component, compAuthName, compCode,
2932
0
                                             numericCode, allowedAuthorities);
2933
0
            sqlStatements.insert(sqlStatements.end(), sqlStatementsTmp.begin(),
2934
0
                                 sqlStatementsTmp.end());
2935
0
        }
2936
2937
0
        componentsId.emplace_back(
2938
0
            std::pair<std::string, std::string>(compAuthName, compCode));
2939
2940
0
        ++counter;
2941
0
    }
2942
2943
    // Insert new record in compound_crs table
2944
0
    const auto sql = formatStatement(
2945
0
        "INSERT INTO compound_crs VALUES("
2946
0
        "'%q','%q','%q','%q','%q','%q','%q','%q',0);",
2947
0
        authName.c_str(), code.c_str(), crs->nameStr().c_str(),
2948
0
        "", // description
2949
0
        componentsId[0].first.c_str(), componentsId[0].second.c_str(),
2950
0
        componentsId[1].first.c_str(), componentsId[1].second.c_str());
2951
0
    appendSql(sqlStatements, sql);
2952
2953
0
    identifyOrInsertUsages(crs, "compound_crs", authName, code,
2954
0
                           allowedAuthorities, sqlStatements);
2955
2956
0
    return sqlStatements;
2957
0
}
2958
2959
//! @endcond
2960
2961
// ---------------------------------------------------------------------------
2962
2963
//! @cond Doxygen_Suppress
2964
823
DatabaseContext::~DatabaseContext() {
2965
823
    try {
2966
823
        stopInsertStatementsSession();
2967
823
    } catch (const std::exception &) {
2968
0
    }
2969
823
}
2970
//! @endcond
2971
2972
// ---------------------------------------------------------------------------
2973
2974
824
DatabaseContext::DatabaseContext() : d(std::make_unique<Private>()) {}
2975
2976
// ---------------------------------------------------------------------------
2977
2978
/** \brief Instantiate a database context.
2979
 *
2980
 * This database context should be used only by one thread at a time.
2981
 *
2982
 * @param databasePath Path and filename of the database. Might be empty
2983
 * string for the default rules to locate the default proj.db
2984
 * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
2985
 * Might be empty.
2986
 * Starting with PROJ 8.1, if this parameter is an empty array,
2987
 * the PROJ_AUX_DB environment variable will be used, if set.
2988
 * It must contain one or several paths. If several paths are
2989
 * provided, they must be separated by the colon (:) character on Unix, and
2990
 * on Windows, by the semi-colon (;) character.
2991
 * @param ctx Context used for file search.
2992
 * @throw FactoryException if the database cannot be opened.
2993
 */
2994
DatabaseContextNNPtr
2995
DatabaseContext::create(const std::string &databasePath,
2996
                        const std::vector<std::string> &auxiliaryDatabasePaths,
2997
824
                        PJ_CONTEXT *ctx) {
2998
824
    auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
2999
824
    auto dbCtxPrivate = dbCtx->getPrivate();
3000
824
    dbCtxPrivate->open(databasePath, ctx);
3001
824
    auto auxDbs(auxiliaryDatabasePaths);
3002
824
    if (auxDbs.empty()) {
3003
824
        const char *auxDbStr = getenv("PROJ_AUX_DB");
3004
824
        if (auxDbStr) {
3005
#ifdef _WIN32
3006
            const char *delim = ";";
3007
#else
3008
0
            const char *delim = ":";
3009
0
#endif
3010
0
            auxDbs = split(auxDbStr, delim);
3011
0
        }
3012
824
    }
3013
824
    if (!auxDbs.empty()) {
3014
0
        dbCtxPrivate->attachExtraDatabases(auxDbs);
3015
0
        dbCtxPrivate->auxiliaryDatabasePaths_ = std::move(auxDbs);
3016
0
    }
3017
824
    dbCtxPrivate->self_ = dbCtx.as_nullable();
3018
824
    return dbCtx;
3019
824
}
3020
3021
// ---------------------------------------------------------------------------
3022
3023
/** \brief Return the list of authorities used in the database.
3024
 */
3025
923
std::set<std::string> DatabaseContext::getAuthorities() const {
3026
923
    auto res = d->run("SELECT auth_name FROM authority_list");
3027
923
    std::set<std::string> list;
3028
7.38k
    for (const auto &row : res) {
3029
7.38k
        list.insert(row[0]);
3030
7.38k
    }
3031
923
    return list;
3032
923
}
3033
3034
// ---------------------------------------------------------------------------
3035
3036
/** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
3037
 * CREATE VIEW) needed to initialize a new database.
3038
 */
3039
0
std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
3040
0
    return d->getDatabaseStructure();
3041
0
}
3042
3043
// ---------------------------------------------------------------------------
3044
3045
/** \brief Return the path to the database.
3046
 */
3047
0
const std::string &DatabaseContext::getPath() const { return d->getPath(); }
3048
3049
// ---------------------------------------------------------------------------
3050
3051
/** \brief Return a metadata item.
3052
 *
3053
 * Value remains valid while this is alive and to the next call to getMetadata
3054
 */
3055
0
const char *DatabaseContext::getMetadata(const char *key) const {
3056
0
    auto res =
3057
0
        d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
3058
0
    if (res.empty()) {
3059
0
        return nullptr;
3060
0
    }
3061
0
    d->lastMetadataValue_ = res.front()[0];
3062
0
    return d->lastMetadataValue_.c_str();
3063
0
}
3064
3065
// ---------------------------------------------------------------------------
3066
3067
/** \brief Starts a session for getInsertStatementsFor()
3068
 *
3069
 * Starts a new session for one or several calls to getInsertStatementsFor().
3070
 * An insertion session guarantees that the inserted objects will not create
3071
 * conflicting intermediate objects.
3072
 *
3073
 * The session must be stopped with stopInsertStatementsSession().
3074
 *
3075
 * Only one session may be active at a time for a given database context.
3076
 *
3077
 * @throw FactoryException in case of error.
3078
 * @since 8.1
3079
 */
3080
0
void DatabaseContext::startInsertStatementsSession() {
3081
0
    if (d->memoryDbHandle_) {
3082
0
        throw FactoryException(
3083
0
            "startInsertStatementsSession() cannot be invoked until "
3084
0
            "stopInsertStatementsSession() is.");
3085
0
    }
3086
3087
0
    d->memoryDbForInsertPath_.clear();
3088
0
    const auto sqlStatements = getDatabaseStructure();
3089
3090
    // Create a in-memory temporary sqlite3 database
3091
0
    std::ostringstream buffer;
3092
0
    buffer << "file:temp_db_for_insert_statements_";
3093
0
    buffer << this;
3094
0
    buffer << ".db?mode=memory&cache=shared";
3095
0
    d->memoryDbForInsertPath_ = buffer.str();
3096
0
    sqlite3 *memoryDbHandle = nullptr;
3097
0
    sqlite3_open_v2(
3098
0
        d->memoryDbForInsertPath_.c_str(), &memoryDbHandle,
3099
0
        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, nullptr);
3100
0
    if (memoryDbHandle == nullptr) {
3101
0
        throw FactoryException("Cannot create in-memory database");
3102
0
    }
3103
0
    d->memoryDbHandle_ =
3104
0
        SQLiteHandle::initFromExistingUniquePtr(memoryDbHandle, true);
3105
3106
    // Fill the structure of this database
3107
0
    for (const auto &sql : sqlStatements) {
3108
0
        char *errmsg = nullptr;
3109
0
        if (sqlite3_exec(d->memoryDbHandle_->handle(), sql.c_str(), nullptr,
3110
0
                         nullptr, &errmsg) != SQLITE_OK) {
3111
0
            const auto sErrMsg =
3112
0
                "Cannot execute " + sql + ": " + (errmsg ? errmsg : "");
3113
0
            sqlite3_free(errmsg);
3114
0
            throw FactoryException(sErrMsg);
3115
0
        }
3116
0
        sqlite3_free(errmsg);
3117
0
    }
3118
3119
    // Attach this database to the current one(s)
3120
0
    auto auxiliaryDatabasePaths(d->auxiliaryDatabasePaths_);
3121
0
    auxiliaryDatabasePaths.push_back(d->memoryDbForInsertPath_);
3122
0
    d->attachExtraDatabases(auxiliaryDatabasePaths);
3123
0
}
3124
3125
// ---------------------------------------------------------------------------
3126
3127
/** \brief Suggests a database code for the passed object.
3128
 *
3129
 * Supported type of objects are PrimeMeridian, Ellipsoid, Datum, DatumEnsemble,
3130
 * GeodeticCRS, ProjectedCRS, VerticalCRS, CompoundCRS, BoundCRS, Conversion.
3131
 *
3132
 * @param object Object for which to suggest a code.
3133
 * @param authName Authority name into which the object will be inserted.
3134
 * @param numericCode Whether the code should be numeric, or derived from the
3135
 * object name.
3136
 * @return the suggested code, that is guaranteed to not conflict with an
3137
 * existing one.
3138
 *
3139
 * @throw FactoryException in case of error.
3140
 * @since 8.1
3141
 */
3142
std::string
3143
DatabaseContext::suggestsCodeFor(const common::IdentifiedObjectNNPtr &object,
3144
                                 const std::string &authName,
3145
0
                                 bool numericCode) {
3146
0
    const char *tableName = "prime_meridian";
3147
0
    if (dynamic_cast<const datum::PrimeMeridian *>(object.get())) {
3148
        // tableName = "prime_meridian";
3149
0
    } else if (dynamic_cast<const datum::Ellipsoid *>(object.get())) {
3150
0
        tableName = "ellipsoid";
3151
0
    } else if (dynamic_cast<const datum::GeodeticReferenceFrame *>(
3152
0
                   object.get())) {
3153
0
        tableName = "geodetic_datum";
3154
0
    } else if (dynamic_cast<const datum::VerticalReferenceFrame *>(
3155
0
                   object.get())) {
3156
0
        tableName = "vertical_datum";
3157
0
    } else if (const auto ensemble =
3158
0
                   dynamic_cast<const datum::DatumEnsemble *>(object.get())) {
3159
0
        const auto &datums = ensemble->datums();
3160
0
        if (!datums.empty() &&
3161
0
            dynamic_cast<const datum::GeodeticReferenceFrame *>(
3162
0
                datums[0].get())) {
3163
0
            tableName = "geodetic_datum";
3164
0
        } else {
3165
0
            tableName = "vertical_datum";
3166
0
        }
3167
0
    } else if (const auto boundCRS =
3168
0
                   dynamic_cast<const crs::BoundCRS *>(object.get())) {
3169
0
        return suggestsCodeFor(boundCRS->baseCRS(), authName, numericCode);
3170
0
    } else if (dynamic_cast<const crs::CRS *>(object.get())) {
3171
0
        tableName = "crs_view";
3172
0
    } else if (dynamic_cast<const operation::Conversion *>(object.get())) {
3173
0
        tableName = "conversion";
3174
0
    } else {
3175
0
        throw FactoryException("suggestsCodeFor(): unhandled type of object");
3176
0
    }
3177
3178
0
    if (numericCode) {
3179
0
        std::string sql("SELECT MAX(code) FROM ");
3180
0
        sql += tableName;
3181
0
        sql += " WHERE auth_name = ? AND code >= '1' AND code <= '999999999' "
3182
0
               "AND upper(code) = lower(code)";
3183
0
        const auto res = d->run(sql, {authName});
3184
0
        if (res.empty()) {
3185
0
            return "1";
3186
0
        }
3187
0
        return toString(atoi(res.front()[0].c_str()) + 1);
3188
0
    }
3189
3190
0
    std::string code;
3191
0
    code.reserve(object->nameStr().size());
3192
0
    bool insertUnderscore = false;
3193
0
    for (const auto ch : toupper(object->nameStr())) {
3194
0
        if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) {
3195
0
            if (insertUnderscore && code.back() != '_')
3196
0
                code += '_';
3197
0
            code += ch;
3198
0
            insertUnderscore = false;
3199
0
        } else {
3200
0
            insertUnderscore = true;
3201
0
        }
3202
0
    }
3203
0
    return d->findFreeCode(tableName, authName, code);
3204
0
}
3205
3206
// ---------------------------------------------------------------------------
3207
3208
/** \brief Returns SQL statements needed to insert the passed object into the
3209
 * database.
3210
 *
3211
 * startInsertStatementsSession() must have been called previously.
3212
 *
3213
 * @param object The object to insert into the database. Currently only
3214
 *               PrimeMeridian, Ellipsoid, Datum, GeodeticCRS, ProjectedCRS,
3215
 *               VerticalCRS, CompoundCRS or BoundCRS are supported.
3216
 * @param authName Authority name into which the object will be inserted.
3217
 * @param code Code with which the object will be inserted.
3218
 * @param numericCode Whether intermediate objects that can be created should
3219
 *                    use numeric codes (true), or may be alphanumeric (false)
3220
 * @param allowedAuthorities Authorities to which intermediate objects are
3221
 *                           allowed to refer to. authName will be implicitly
3222
 *                           added to it. Note that unit, coordinate
3223
 *                           systems, projection methods and parameters will in
3224
 *                           any case be allowed to refer to EPSG.
3225
 * @throw FactoryException in case of error.
3226
 * @since 8.1
3227
 */
3228
std::vector<std::string> DatabaseContext::getInsertStatementsFor(
3229
    const common::IdentifiedObjectNNPtr &object, const std::string &authName,
3230
    const std::string &code, bool numericCode,
3231
0
    const std::vector<std::string> &allowedAuthorities) {
3232
0
    if (d->memoryDbHandle_ == nullptr) {
3233
0
        throw FactoryException(
3234
0
            "startInsertStatementsSession() should be invoked first");
3235
0
    }
3236
3237
0
    const auto crs = util::nn_dynamic_pointer_cast<crs::CRS>(object);
3238
0
    if (crs) {
3239
        // Check if the object is already known under that code
3240
0
        const auto self = NN_NO_CHECK(d->self_.lock());
3241
0
        auto allowedAuthoritiesTmp(allowedAuthorities);
3242
0
        allowedAuthoritiesTmp.emplace_back(authName);
3243
0
        for (const auto &allowedAuthority : allowedAuthoritiesTmp) {
3244
0
            const auto factory =
3245
0
                AuthorityFactory::create(self, allowedAuthority);
3246
0
            const auto candidates = crs->identify(factory);
3247
0
            for (const auto &candidate : candidates) {
3248
0
                if (candidate.second == 100) {
3249
0
                    const auto &ids = candidate.first->identifiers();
3250
0
                    for (const auto &id : ids) {
3251
0
                        if (*(id->codeSpace()) == authName &&
3252
0
                            id->code() == code) {
3253
0
                            return {};
3254
0
                        }
3255
0
                    }
3256
0
                }
3257
0
            }
3258
0
        }
3259
0
    }
3260
3261
0
    if (const auto pm =
3262
0
            util::nn_dynamic_pointer_cast<datum::PrimeMeridian>(object)) {
3263
0
        return d->getInsertStatementsFor(NN_NO_CHECK(pm), authName, code,
3264
0
                                         numericCode, allowedAuthorities);
3265
0
    }
3266
3267
0
    else if (const auto ellipsoid =
3268
0
                 util::nn_dynamic_pointer_cast<datum::Ellipsoid>(object)) {
3269
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ellipsoid), authName, code,
3270
0
                                         numericCode, allowedAuthorities);
3271
0
    }
3272
3273
0
    else if (const auto geodeticDatum =
3274
0
                 util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
3275
0
                     object)) {
3276
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodeticDatum), authName,
3277
0
                                         code, numericCode, allowedAuthorities);
3278
0
    }
3279
3280
0
    else if (const auto ensemble =
3281
0
                 util::nn_dynamic_pointer_cast<datum::DatumEnsemble>(object)) {
3282
0
        return d->getInsertStatementsFor(NN_NO_CHECK(ensemble), authName, code,
3283
0
                                         numericCode, allowedAuthorities);
3284
0
    }
3285
3286
0
    else if (const auto geodCRS =
3287
0
                 std::dynamic_pointer_cast<crs::GeodeticCRS>(crs)) {
3288
0
        return d->getInsertStatementsFor(NN_NO_CHECK(geodCRS), authName, code,
3289
0
                                         numericCode, allowedAuthorities);
3290
0
    }
3291
3292
0
    else if (const auto projCRS =
3293
0
                 std::dynamic_pointer_cast<crs::ProjectedCRS>(crs)) {
3294
0
        return d->getInsertStatementsFor(NN_NO_CHECK(projCRS), authName, code,
3295
0
                                         numericCode, allowedAuthorities);
3296
0
    }
3297
3298
0
    else if (const auto verticalDatum =
3299
0
                 util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3300
0
                     object)) {
3301
0
        return d->getInsertStatementsFor(NN_NO_CHECK(verticalDatum), authName,
3302
0
                                         code, numericCode, allowedAuthorities);
3303
0
    }
3304
3305
0
    else if (const auto vertCRS =
3306
0
                 std::dynamic_pointer_cast<crs::VerticalCRS>(crs)) {
3307
0
        return d->getInsertStatementsFor(NN_NO_CHECK(vertCRS), authName, code,
3308
0
                                         numericCode, allowedAuthorities);
3309
0
    }
3310
3311
0
    else if (const auto compoundCRS =
3312
0
                 std::dynamic_pointer_cast<crs::CompoundCRS>(crs)) {
3313
0
        return d->getInsertStatementsFor(NN_NO_CHECK(compoundCRS), authName,
3314
0
                                         code, numericCode, allowedAuthorities);
3315
0
    }
3316
3317
0
    else if (const auto boundCRS =
3318
0
                 std::dynamic_pointer_cast<crs::BoundCRS>(crs)) {
3319
0
        return getInsertStatementsFor(boundCRS->baseCRS(), authName, code,
3320
0
                                      numericCode, allowedAuthorities);
3321
0
    }
3322
3323
0
    else {
3324
0
        throw FactoryException(
3325
0
            "getInsertStatementsFor(): unhandled type of object");
3326
0
    }
3327
0
}
3328
3329
// ---------------------------------------------------------------------------
3330
3331
/** \brief Stops an insertion session started with
3332
 * startInsertStatementsSession()
3333
 *
3334
 * @since 8.1
3335
 */
3336
823
void DatabaseContext::stopInsertStatementsSession() {
3337
823
    if (d->memoryDbHandle_) {
3338
0
        d->clearCaches();
3339
0
        d->attachExtraDatabases(d->auxiliaryDatabasePaths_);
3340
0
        d->memoryDbHandle_.reset();
3341
0
        d->memoryDbForInsertPath_.clear();
3342
0
    }
3343
823
}
3344
3345
// ---------------------------------------------------------------------------
3346
3347
//! @cond Doxygen_Suppress
3348
3349
0
DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
3350
0
    auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
3351
0
    ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
3352
0
    return ctxt;
3353
0
}
3354
3355
// ---------------------------------------------------------------------------
3356
3357
0
void *DatabaseContext::getSqliteHandle() const { return d->handle()->handle(); }
3358
3359
// ---------------------------------------------------------------------------
3360
3361
bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
3362
                                             std::string &projFilename,
3363
                                             std::string &projFormat,
3364
403
                                             bool &inverse) const {
3365
403
    auto res = d->run(
3366
403
        "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
3367
403
        "grid_alternatives WHERE original_grid_name = ? AND "
3368
403
        "proj_grid_name <> ''",
3369
403
        {officialName});
3370
403
    if (res.empty()) {
3371
81
        return false;
3372
81
    }
3373
322
    const auto &row = res.front();
3374
322
    projFilename = row[0];
3375
322
    projFormat = row[1];
3376
322
    inverse = row[2] == "1";
3377
322
    return true;
3378
403
}
3379
3380
// ---------------------------------------------------------------------------
3381
3382
static std::string makeCachedGridKey(const std::string &projFilename,
3383
                                     bool networkEnabled,
3384
0
                                     bool considerKnownGridsAsAvailable) {
3385
0
    std::string key(projFilename);
3386
0
    key += networkEnabled ? "true" : "false";
3387
0
    key += considerKnownGridsAsAvailable ? "true" : "false";
3388
0
    return key;
3389
0
}
3390
3391
// ---------------------------------------------------------------------------
3392
3393
/** Invalidates information related to projFilename that might have been
3394
 * previously cached by lookForGridInfo().
3395
 *
3396
 * This is useful when downloading a new grid during a session.
3397
 */
3398
0
void DatabaseContext::invalidateGridInfo(const std::string &projFilename) {
3399
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, false));
3400
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, false, true));
3401
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, false));
3402
0
    d->evictGridInfoFromCache(makeCachedGridKey(projFilename, true, true));
3403
0
}
3404
3405
// ---------------------------------------------------------------------------
3406
3407
bool DatabaseContext::lookForGridInfo(
3408
    const std::string &projFilename, bool considerKnownGridsAsAvailable,
3409
    std::string &fullFilename, std::string &packageName, std::string &url,
3410
0
    bool &directDownload, bool &openLicense, bool &gridAvailable) const {
3411
0
    Private::GridInfoCache info;
3412
3413
0
    if (projFilename == "null") {
3414
        // Special case for implicit "null" grid.
3415
0
        fullFilename.clear();
3416
0
        packageName.clear();
3417
0
        url.clear();
3418
0
        directDownload = false;
3419
0
        openLicense = true;
3420
0
        gridAvailable = true;
3421
0
        return true;
3422
0
    }
3423
3424
0
    auto ctxt = d->pjCtxt();
3425
0
    if (ctxt == nullptr) {
3426
0
        ctxt = pj_get_default_ctx();
3427
0
        d->setPjCtxt(ctxt);
3428
0
    }
3429
3430
0
    const std::string key(makeCachedGridKey(
3431
0
        projFilename, proj_context_is_network_enabled(ctxt) != false,
3432
0
        considerKnownGridsAsAvailable));
3433
0
    if (d->getGridInfoFromCache(key, info)) {
3434
0
        fullFilename = info.fullFilename;
3435
0
        packageName = info.packageName;
3436
0
        url = info.url;
3437
0
        directDownload = info.directDownload;
3438
0
        openLicense = info.openLicense;
3439
0
        gridAvailable = info.gridAvailable;
3440
0
        return info.found;
3441
0
    }
3442
3443
0
    fullFilename.clear();
3444
0
    packageName.clear();
3445
0
    url.clear();
3446
0
    openLicense = false;
3447
0
    directDownload = false;
3448
0
    gridAvailable = false;
3449
3450
0
    const auto resolveFullFilename = [ctxt, &fullFilename, &projFilename]() {
3451
0
        fullFilename.resize(2048);
3452
0
        const int errno_before = proj_context_errno(ctxt);
3453
0
        bool lGridAvailable = NS_PROJ::FileManager::open_resource_file(
3454
0
                                  ctxt, projFilename.c_str(), &fullFilename[0],
3455
0
                                  fullFilename.size() - 1) != nullptr;
3456
0
        proj_context_errno_set(ctxt, errno_before);
3457
0
        fullFilename.resize(strlen(fullFilename.c_str()));
3458
0
        return lGridAvailable;
3459
0
    };
3460
3461
0
    auto res =
3462
0
        d->run("SELECT "
3463
0
               "grid_packages.package_name, "
3464
0
               "grid_alternatives.url, "
3465
0
               "grid_packages.url AS package_url, "
3466
0
               "grid_alternatives.open_license, "
3467
0
               "grid_packages.open_license AS package_open_license, "
3468
0
               "grid_alternatives.direct_download, "
3469
0
               "grid_packages.direct_download AS package_direct_download, "
3470
0
               "grid_alternatives.proj_grid_name, "
3471
0
               "grid_alternatives.old_proj_grid_name "
3472
0
               "FROM grid_alternatives "
3473
0
               "LEFT JOIN grid_packages ON "
3474
0
               "grid_alternatives.package_name = grid_packages.package_name "
3475
0
               "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
3476
0
               {projFilename, projFilename});
3477
0
    bool ret = !res.empty();
3478
0
    if (ret) {
3479
0
        const auto &row = res.front();
3480
0
        packageName = std::move(row[0]);
3481
0
        url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
3482
0
        openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
3483
0
        directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
3484
3485
0
        const auto &proj_grid_name = row[7];
3486
0
        const auto &old_proj_grid_name = row[8];
3487
0
        if (proj_grid_name != old_proj_grid_name &&
3488
0
            old_proj_grid_name == projFilename) {
3489
0
            std::string fullFilenameNewName;
3490
0
            fullFilenameNewName.resize(2048);
3491
0
            const int errno_before = proj_context_errno(ctxt);
3492
0
            bool gridAvailableWithNewName =
3493
0
                pj_find_file(ctxt, proj_grid_name.c_str(),
3494
0
                             &fullFilenameNewName[0],
3495
0
                             fullFilenameNewName.size() - 1) != 0;
3496
0
            proj_context_errno_set(ctxt, errno_before);
3497
0
            fullFilenameNewName.resize(strlen(fullFilenameNewName.c_str()));
3498
0
            if (gridAvailableWithNewName) {
3499
0
                gridAvailable = true;
3500
0
                fullFilename = std::move(fullFilenameNewName);
3501
0
            }
3502
0
        }
3503
3504
0
        if (!gridAvailable && considerKnownGridsAsAvailable &&
3505
0
            (!packageName.empty() || (!url.empty() && openLicense))) {
3506
3507
            // In considerKnownGridsAsAvailable mode, try to fetch the local
3508
            // file name if it exists, but do not attempt network access.
3509
0
            const auto network_was_enabled =
3510
0
                proj_context_is_network_enabled(ctxt);
3511
0
            proj_context_set_enable_network(ctxt, false);
3512
0
            (void)resolveFullFilename();
3513
0
            proj_context_set_enable_network(ctxt, network_was_enabled);
3514
3515
0
            gridAvailable = true;
3516
0
        }
3517
3518
0
        info.packageName = packageName;
3519
0
        std::string endpoint(proj_context_get_url_endpoint(d->pjCtxt()));
3520
0
        if (!endpoint.empty() && starts_with(url, "https://cdn.proj.org/")) {
3521
0
            if (endpoint.back() != '/') {
3522
0
                endpoint += '/';
3523
0
            }
3524
0
            url = endpoint + url.substr(strlen("https://cdn.proj.org/"));
3525
0
        }
3526
0
        info.directDownload = directDownload;
3527
0
        info.openLicense = openLicense;
3528
3529
0
        if (!gridAvailable) {
3530
0
            gridAvailable = resolveFullFilename();
3531
0
        }
3532
0
    } else {
3533
0
        gridAvailable = resolveFullFilename();
3534
3535
0
        if (starts_with(fullFilename, "http://") ||
3536
0
            starts_with(fullFilename, "https://")) {
3537
0
            url = fullFilename;
3538
0
            fullFilename.clear();
3539
0
        }
3540
0
    }
3541
3542
0
    info.fullFilename = fullFilename;
3543
0
    info.url = url;
3544
0
    info.gridAvailable = gridAvailable;
3545
0
    info.found = ret;
3546
0
    d->cache(key, info);
3547
0
    return ret;
3548
0
}
3549
3550
// ---------------------------------------------------------------------------
3551
3552
/** Returns the number of queries to the database since the creation of this
3553
 * instance.
3554
 */
3555
0
unsigned int DatabaseContext::getQueryCounter() const {
3556
0
    return d->queryCounter_;
3557
0
}
3558
3559
// ---------------------------------------------------------------------------
3560
3561
bool DatabaseContext::isKnownName(const std::string &name,
3562
0
                                  const std::string &tableName) const {
3563
0
    std::string sql("SELECT 1 FROM \"");
3564
0
    sql += replaceAll(tableName, "\"", "\"\"");
3565
0
    sql += "\" WHERE name = ? LIMIT 1";
3566
0
    return !d->run(sql, {name}).empty();
3567
0
}
3568
// ---------------------------------------------------------------------------
3569
3570
std::string
3571
40.5k
DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
3572
40.5k
    auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
3573
40.5k
                      "old_proj_grid_name = ?",
3574
40.5k
                      {oldProjGridName});
3575
40.5k
    if (res.empty()) {
3576
40.5k
        return std::string();
3577
40.5k
    }
3578
4
    return res.front()[0];
3579
40.5k
}
3580
3581
// ---------------------------------------------------------------------------
3582
3583
3.22k
std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
3584
3.22k
    auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
3585
3.22k
                      "proj_grid_name = ?",
3586
3.22k
                      {gridName});
3587
3.22k
    if (res.empty()) {
3588
3.18k
        return std::string();
3589
3.18k
    }
3590
44
    return res.front()[0];
3591
3.22k
}
3592
3593
// ---------------------------------------------------------------------------
3594
3595
// scripts/build_db_from_esri.py adds a second alias for
3596
// names that have '[' in them. See get_old_esri_name()
3597
// in scripts/build_db_from_esri.py
3598
// So if we only have two aliases detect that situation to get the official
3599
// new name
3600
0
static std::string getUniqueEsriAlias(const std::list<std::string> &l) {
3601
0
    std::string first = l.front();
3602
0
    std::string second = *(std::next(l.begin()));
3603
0
    if (second.find('[') != std::string::npos)
3604
0
        std::swap(first, second);
3605
0
    if (replaceAll(replaceAll(replaceAll(first, "[", ""), "]", ""), "-", "_") ==
3606
0
        second) {
3607
0
        return first;
3608
0
    }
3609
0
    return std::string();
3610
0
}
3611
3612
// ---------------------------------------------------------------------------
3613
3614
/** \brief Gets the alias name from an official name.
3615
 *
3616
 * @param officialName Official name. Mandatory
3617
 * @param tableName Table name/category. Mandatory.
3618
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3619
 *                  accepted as special names to add a constraint on the "type"
3620
 *                  column of the "geodetic_crs" table.
3621
 * @param source Source of the alias. Mandatory
3622
 * @return Alias name (or empty if not found).
3623
 * @throw FactoryException in case of error.
3624
 */
3625
std::string
3626
DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
3627
                                          const std::string &tableName,
3628
0
                                          const std::string &source) const {
3629
0
    std::string sql("SELECT auth_name, code FROM \"");
3630
0
    const auto genuineTableName =
3631
0
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3632
0
            ? "geodetic_crs"
3633
0
            : tableName;
3634
0
    sql += replaceAll(genuineTableName, "\"", "\"\"");
3635
0
    sql += "\" WHERE name = ?";
3636
0
    if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3637
0
        sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3638
0
    } else if (tableName == "geographic_3D_crs") {
3639
0
        sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3640
0
    }
3641
0
    sql += " ORDER BY deprecated";
3642
0
    auto res = d->run(sql, {officialName});
3643
    // Sorry for the hack excluding NAD83 + geographic_3D_crs, but otherwise
3644
    // EPSG has a weird alias from NAD83 to EPSG:4152 which happens to be
3645
    // NAD83(HARN), and that's definitely not desirable.
3646
0
    if (res.empty() &&
3647
0
        !(officialName == "NAD83" && tableName == "geographic_3D_crs")) {
3648
0
        res = d->run(
3649
0
            "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
3650
0
            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3651
0
            {genuineTableName, officialName});
3652
0
        if (res.size() != 1) {
3653
0
            return std::string();
3654
0
        }
3655
0
    }
3656
0
    for (const auto &row : res) {
3657
0
        auto res2 =
3658
0
            d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3659
0
                   "auth_name = ? AND code = ? AND source = ?",
3660
0
                   {genuineTableName, row[0], row[1], source});
3661
0
        if (!res2.empty()) {
3662
0
            if (res2.size() == 2 && source == "ESRI") {
3663
0
                std::list<std::string> l;
3664
0
                l.emplace_back(res2.front()[0]);
3665
0
                l.emplace_back((*(std::next(res2.begin())))[0]);
3666
0
                std::string uniqueEsriAlias = getUniqueEsriAlias(l);
3667
0
                if (!uniqueEsriAlias.empty())
3668
0
                    return uniqueEsriAlias;
3669
0
            }
3670
0
            return res2.front()[0];
3671
0
        }
3672
0
    }
3673
0
    return std::string();
3674
0
}
3675
3676
// ---------------------------------------------------------------------------
3677
3678
/** \brief Gets the alias names for an object.
3679
 *
3680
 * Either authName + code or officialName must be non empty.
3681
 *
3682
 * @param authName Authority.
3683
 * @param code Code.
3684
 * @param officialName Official name.
3685
 * @param tableName Table name/category. Mandatory.
3686
 *                  "geographic_2D_crs" and "geographic_3D_crs" are also
3687
 *                  accepted as special names to add a constraint on the "type"
3688
 *                  column of the "geodetic_crs" table.
3689
 * @param source Source of the alias. May be empty.
3690
 * @return Aliases
3691
 */
3692
std::list<std::string> DatabaseContext::getAliases(
3693
    const std::string &authName, const std::string &code,
3694
    const std::string &officialName, const std::string &tableName,
3695
837
    const std::string &source) const {
3696
3697
837
    std::list<std::string> res;
3698
837
    const auto key(authName + code + officialName + tableName + source);
3699
837
    if (d->cacheAliasNames_.tryGet(key, res)) {
3700
808
        return res;
3701
808
    }
3702
3703
29
    std::string resolvedAuthName(authName);
3704
29
    std::string resolvedCode(code);
3705
29
    const auto genuineTableName =
3706
29
        tableName == "geographic_2D_crs" || tableName == "geographic_3D_crs"
3707
29
            ? "geodetic_crs"
3708
29
            : tableName;
3709
29
    if (authName.empty() || code.empty()) {
3710
0
        std::string sql("SELECT auth_name, code FROM \"");
3711
0
        sql += replaceAll(genuineTableName, "\"", "\"\"");
3712
0
        sql += "\" WHERE name = ?";
3713
0
        if (tableName == "geodetic_crs" || tableName == "geographic_2D_crs") {
3714
0
            sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
3715
0
        } else if (tableName == "geographic_3D_crs") {
3716
0
            sql += " AND type = " GEOG_3D_SINGLE_QUOTED;
3717
0
        }
3718
0
        sql += " ORDER BY deprecated";
3719
0
        auto resSql = d->run(sql, {officialName});
3720
0
        if (resSql.empty()) {
3721
0
            resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
3722
0
                            "table_name = ? AND "
3723
0
                            "alt_name = ? AND source IN ('EPSG', 'PROJ')",
3724
0
                            {genuineTableName, officialName});
3725
0
            if (resSql.size() != 1) {
3726
0
                d->cacheAliasNames_.insert(key, res);
3727
0
                return res;
3728
0
            }
3729
0
        }
3730
0
        const auto &row = resSql.front();
3731
0
        resolvedAuthName = row[0];
3732
0
        resolvedCode = row[1];
3733
0
    }
3734
29
    std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
3735
29
                    "auth_name = ? AND code = ?");
3736
29
    ListOfParams params{genuineTableName, resolvedAuthName, resolvedCode};
3737
29
    if (!source.empty()) {
3738
0
        sql += " AND source = ?";
3739
0
        params.emplace_back(source);
3740
0
    }
3741
29
    auto resSql = d->run(sql, params);
3742
41
    for (const auto &row : resSql) {
3743
41
        res.emplace_back(row[0]);
3744
41
    }
3745
3746
29
    if (res.size() == 2 && source == "ESRI") {
3747
0
        const auto uniqueEsriAlias = getUniqueEsriAlias(res);
3748
0
        if (!uniqueEsriAlias.empty()) {
3749
0
            res.clear();
3750
0
            res.emplace_back(uniqueEsriAlias);
3751
0
        }
3752
0
    }
3753
3754
29
    d->cacheAliasNames_.insert(key, res);
3755
29
    return res;
3756
29
}
3757
3758
// ---------------------------------------------------------------------------
3759
3760
/** \brief Return the 'name' column of a table for an object
3761
 *
3762
 * @param tableName Table name/category.
3763
 * @param authName Authority name of the object.
3764
 * @param code Code of the object
3765
 * @return Name (or empty)
3766
 * @throw FactoryException in case of error.
3767
 */
3768
std::string DatabaseContext::getName(const std::string &tableName,
3769
                                     const std::string &authName,
3770
0
                                     const std::string &code) const {
3771
0
    std::string res;
3772
0
    const auto key(tableName + authName + code);
3773
0
    if (d->cacheNames_.tryGet(key, res)) {
3774
0
        return res;
3775
0
    }
3776
3777
0
    std::string sql("SELECT name FROM \"");
3778
0
    sql += replaceAll(tableName, "\"", "\"\"");
3779
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3780
0
    auto sqlRes = d->run(sql, {authName, code});
3781
0
    if (sqlRes.empty()) {
3782
0
        res.clear();
3783
0
    } else {
3784
0
        res = sqlRes.front()[0];
3785
0
    }
3786
0
    d->cacheNames_.insert(key, res);
3787
0
    return res;
3788
0
}
3789
3790
// ---------------------------------------------------------------------------
3791
3792
/** \brief Return the 'text_definition' column of a table for an object
3793
 *
3794
 * @param tableName Table name/category.
3795
 * @param authName Authority name of the object.
3796
 * @param code Code of the object
3797
 * @return Text definition (or empty)
3798
 * @throw FactoryException in case of error.
3799
 */
3800
std::string DatabaseContext::getTextDefinition(const std::string &tableName,
3801
                                               const std::string &authName,
3802
0
                                               const std::string &code) const {
3803
0
    std::string sql("SELECT text_definition FROM \"");
3804
0
    sql += replaceAll(tableName, "\"", "\"\"");
3805
0
    sql += "\" WHERE auth_name = ? AND code = ?";
3806
0
    auto res = d->run(sql, {authName, code});
3807
0
    if (res.empty()) {
3808
0
        return std::string();
3809
0
    }
3810
0
    return res.front()[0];
3811
0
}
3812
3813
// ---------------------------------------------------------------------------
3814
3815
/** \brief Return the allowed authorities when researching transformations
3816
 * between different authorities.
3817
 *
3818
 * @throw FactoryException in case of error.
3819
 */
3820
std::vector<std::string> DatabaseContext::getAllowedAuthorities(
3821
    const std::string &sourceAuthName,
3822
0
    const std::string &targetAuthName) const {
3823
3824
0
    const auto key(sourceAuthName + targetAuthName);
3825
0
    auto hit = d->cacheAllowedAuthorities_.find(key);
3826
0
    if (hit != d->cacheAllowedAuthorities_.end()) {
3827
0
        return hit->second;
3828
0
    }
3829
3830
0
    auto sqlRes = d->run(
3831
0
        "SELECT allowed_authorities FROM authority_to_authority_preference "
3832
0
        "WHERE source_auth_name = ? AND target_auth_name = ?",
3833
0
        {sourceAuthName, targetAuthName});
3834
0
    if (sqlRes.empty()) {
3835
0
        sqlRes = d->run(
3836
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3837
0
            "WHERE source_auth_name = ? AND target_auth_name = 'any'",
3838
0
            {sourceAuthName});
3839
0
    }
3840
0
    if (sqlRes.empty()) {
3841
0
        sqlRes = d->run(
3842
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3843
0
            "WHERE source_auth_name = 'any' AND target_auth_name = ?",
3844
0
            {targetAuthName});
3845
0
    }
3846
0
    if (sqlRes.empty()) {
3847
0
        sqlRes = d->run(
3848
0
            "SELECT allowed_authorities FROM authority_to_authority_preference "
3849
0
            "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
3850
0
            {});
3851
0
    }
3852
0
    if (sqlRes.empty()) {
3853
0
        d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
3854
0
        return std::vector<std::string>();
3855
0
    }
3856
0
    auto res = split(sqlRes.front()[0], ',');
3857
0
    d->cacheAllowedAuthorities_[key] = res;
3858
0
    return res;
3859
0
}
3860
3861
// ---------------------------------------------------------------------------
3862
3863
std::list<std::pair<std::string, std::string>>
3864
DatabaseContext::getNonDeprecated(const std::string &tableName,
3865
                                  const std::string &authName,
3866
0
                                  const std::string &code) const {
3867
0
    auto sqlRes =
3868
0
        d->run("SELECT replacement_auth_name, replacement_code, source "
3869
0
               "FROM deprecation "
3870
0
               "WHERE table_name = ? AND deprecated_auth_name = ? "
3871
0
               "AND deprecated_code = ?",
3872
0
               {tableName, authName, code});
3873
0
    std::list<std::pair<std::string, std::string>> res;
3874
0
    for (const auto &row : sqlRes) {
3875
0
        const auto &source = row[2];
3876
0
        if (source == "PROJ") {
3877
0
            const auto &replacement_auth_name = row[0];
3878
0
            const auto &replacement_code = row[1];
3879
0
            res.emplace_back(replacement_auth_name, replacement_code);
3880
0
        }
3881
0
    }
3882
0
    if (!res.empty()) {
3883
0
        return res;
3884
0
    }
3885
0
    for (const auto &row : sqlRes) {
3886
0
        const auto &replacement_auth_name = row[0];
3887
0
        const auto &replacement_code = row[1];
3888
0
        res.emplace_back(replacement_auth_name, replacement_code);
3889
0
    }
3890
0
    return res;
3891
0
}
3892
3893
// ---------------------------------------------------------------------------
3894
3895
const std::vector<DatabaseContext::Private::VersionedAuthName> &
3896
4.06k
DatabaseContext::Private::getCacheAuthNameWithVersion() {
3897
4.06k
    if (cacheAuthNameWithVersion_.empty()) {
3898
57
        const auto sqlRes =
3899
57
            run("SELECT versioned_auth_name, auth_name, version, priority "
3900
57
                "FROM versioned_auth_name_mapping");
3901
57
        for (const auto &row : sqlRes) {
3902
57
            VersionedAuthName van;
3903
57
            van.versionedAuthName = row[0];
3904
57
            van.authName = row[1];
3905
57
            van.version = row[2];
3906
57
            van.priority = atoi(row[3].c_str());
3907
57
            cacheAuthNameWithVersion_.emplace_back(std::move(van));
3908
57
        }
3909
57
    }
3910
4.06k
    return cacheAuthNameWithVersion_;
3911
4.06k
}
3912
3913
// ---------------------------------------------------------------------------
3914
3915
// From IAU_2015 returns (IAU,2015)
3916
bool DatabaseContext::getAuthorityAndVersion(
3917
    const std::string &versionedAuthName, std::string &authNameOut,
3918
0
    std::string &versionOut) {
3919
3920
0
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3921
0
        if (van.versionedAuthName == versionedAuthName) {
3922
0
            authNameOut = van.authName;
3923
0
            versionOut = van.version;
3924
0
            return true;
3925
0
        }
3926
0
    }
3927
0
    return false;
3928
0
}
3929
3930
// ---------------------------------------------------------------------------
3931
3932
// From IAU and 2015, returns IAU_2015
3933
bool DatabaseContext::getVersionedAuthority(const std::string &authName,
3934
                                            const std::string &version,
3935
3.12k
                                            std::string &versionedAuthNameOut) {
3936
3937
3.12k
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3938
3.12k
        if (van.authName == authName && van.version == version) {
3939
0
            versionedAuthNameOut = van.versionedAuthName;
3940
0
            return true;
3941
0
        }
3942
3.12k
    }
3943
3.12k
    return false;
3944
3.12k
}
3945
3946
// ---------------------------------------------------------------------------
3947
3948
// From IAU returns IAU_latest, ... IAU_2015
3949
std::vector<std::string>
3950
940
DatabaseContext::getVersionedAuthoritiesFromName(const std::string &authName) {
3951
3952
940
    typedef std::pair<std::string, int> VersionedAuthNamePriority;
3953
940
    std::vector<VersionedAuthNamePriority> tmp;
3954
940
    for (const auto &van : d->getCacheAuthNameWithVersion()) {
3955
940
        if (van.authName == authName) {
3956
0
            tmp.emplace_back(
3957
0
                VersionedAuthNamePriority(van.versionedAuthName, van.priority));
3958
0
        }
3959
940
    }
3960
940
    std::vector<std::string> res;
3961
940
    if (!tmp.empty()) {
3962
        // Sort by decreasing priority
3963
0
        std::sort(tmp.begin(), tmp.end(),
3964
0
                  [](const VersionedAuthNamePriority &a,
3965
0
                     const VersionedAuthNamePriority &b) {
3966
0
                      return b.second > a.second;
3967
0
                  });
3968
0
        for (const auto &pair : tmp)
3969
0
            res.emplace_back(pair.first);
3970
0
    }
3971
940
    return res;
3972
940
}
3973
3974
// ---------------------------------------------------------------------------
3975
3976
std::vector<operation::CoordinateOperationNNPtr>
3977
DatabaseContext::getTransformationsForGridName(
3978
0
    const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
3979
0
    auto sqlRes = databaseContext->d->run(
3980
0
        "SELECT auth_name, code FROM grid_transformation "
3981
0
        "WHERE grid_name = ? OR grid_name IN "
3982
0
        "(SELECT original_grid_name FROM grid_alternatives "
3983
0
        "WHERE proj_grid_name = ?) ORDER BY auth_name, code",
3984
0
        {gridName, gridName});
3985
0
    std::vector<operation::CoordinateOperationNNPtr> res;
3986
0
    for (const auto &row : sqlRes) {
3987
0
        res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
3988
0
                             ->createCoordinateOperation(row[1], true));
3989
0
    }
3990
0
    return res;
3991
0
}
3992
3993
// ---------------------------------------------------------------------------
3994
3995
// Fixes wrong towgs84 values returned by epsg.io when using a Coordinate Frame
3996
// transformation, where they neglect to reverse the sign of the rotation terms.
3997
// Cf https://github.com/OSGeo/PROJ/issues/4170 and
3998
// https://github.com/maptiler/epsg.io/issues/194
3999
// We do that only when we found a valid Coordinate Frame rotation that
4000
// has the same numeric values (and no corresponding Position Vector
4001
// transformation with same values, or Coordinate Frame transformation with
4002
// opposite sign for rotation terms, both are highly unlikely)
4003
bool DatabaseContext::toWGS84AutocorrectWrongValues(
4004
    double &tx, double &ty, double &tz, double &rx, double &ry, double &rz,
4005
2.08k
    double &scale_difference) const {
4006
2.08k
    if (rx == 0 && ry == 0 && rz == 0)
4007
1.83k
        return false;
4008
    // 9606: Position Vector transformation (geog2D domain)
4009
    // 9607: Coordinate Frame rotation (geog2D domain)
4010
252
    std::string sql(
4011
252
        "SELECT DISTINCT method_code "
4012
252
        "FROM helmert_transformation_table WHERE "
4013
252
        "abs(tx - ?) <= 1e-8 * abs(tx) AND "
4014
252
        "abs(ty - ?) <= 1e-8 * abs(ty) AND "
4015
252
        "abs(tz - ?) <= 1e-8 * abs(tz) AND "
4016
252
        "abs(rx - ?) <= 1e-8 * abs(rx) AND "
4017
252
        "abs(ry - ?) <= 1e-8 * abs(ry) AND "
4018
252
        "abs(rz - ?) <= 1e-8 * abs(rz) AND "
4019
252
        "abs(scale_difference - ?) <= 1e-8 * abs(scale_difference) AND "
4020
252
        "method_auth_name = 'EPSG' AND "
4021
252
        "method_code IN (9606, 9607) AND "
4022
252
        "translation_uom_auth_name = 'EPSG' AND "
4023
252
        "translation_uom_code = 9001 AND " // metre
4024
252
        "rotation_uom_auth_name = 'EPSG' AND "
4025
252
        "rotation_uom_code = 9104 AND " // arc-second
4026
252
        "scale_difference_uom_auth_name = 'EPSG' AND "
4027
252
        "scale_difference_uom_code = 9202 AND " // parts per million
4028
252
        "deprecated = 0");
4029
252
    ListOfParams params;
4030
252
    params.emplace_back(tx);
4031
252
    params.emplace_back(ty);
4032
252
    params.emplace_back(tz);
4033
252
    params.emplace_back(rx);
4034
252
    params.emplace_back(ry);
4035
252
    params.emplace_back(rz);
4036
252
    params.emplace_back(scale_difference);
4037
252
    bool bFound9606 = false;
4038
252
    bool bFound9607 = false;
4039
252
    for (const auto &row : d->run(sql, params)) {
4040
0
        if (row[0] == "9606") {
4041
0
            bFound9606 = true;
4042
0
        } else if (row[0] == "9607") {
4043
0
            bFound9607 = true;
4044
0
        }
4045
0
    }
4046
252
    if (bFound9607 && !bFound9606) {
4047
0
        params.clear();
4048
0
        params.emplace_back(tx);
4049
0
        params.emplace_back(ty);
4050
0
        params.emplace_back(tz);
4051
0
        params.emplace_back(-rx);
4052
0
        params.emplace_back(-ry);
4053
0
        params.emplace_back(-rz);
4054
0
        params.emplace_back(scale_difference);
4055
0
        if (d->run(sql, params).empty()) {
4056
0
            if (d->pjCtxt()) {
4057
0
                pj_log(d->pjCtxt(), PJ_LOG_ERROR,
4058
0
                       "Auto-correcting wrong sign of rotation terms of "
4059
0
                       "TOWGS84 clause from %s,%s,%s,%s,%s,%s,%s to "
4060
0
                       "%s,%s,%s,%s,%s,%s,%s",
4061
0
                       internal::toString(tx).c_str(),
4062
0
                       internal::toString(ty).c_str(),
4063
0
                       internal::toString(tz).c_str(),
4064
0
                       internal::toString(rx).c_str(),
4065
0
                       internal::toString(ry).c_str(),
4066
0
                       internal::toString(rz).c_str(),
4067
0
                       internal::toString(scale_difference).c_str(),
4068
0
                       internal::toString(tx).c_str(),
4069
0
                       internal::toString(ty).c_str(),
4070
0
                       internal::toString(tz).c_str(),
4071
0
                       internal::toString(-rx).c_str(),
4072
0
                       internal::toString(-ry).c_str(),
4073
0
                       internal::toString(-rz).c_str(),
4074
0
                       internal::toString(scale_difference).c_str());
4075
0
            }
4076
0
            rx = -rx;
4077
0
            ry = -ry;
4078
0
            rz = -rz;
4079
0
            return true;
4080
0
        }
4081
0
    }
4082
252
    return false;
4083
252
}
4084
4085
//! @endcond
4086
4087
// ---------------------------------------------------------------------------
4088
4089
//! @cond Doxygen_Suppress
4090
struct AuthorityFactory::Private {
4091
    Private(const DatabaseContextNNPtr &contextIn,
4092
            const std::string &authorityName)
4093
40.2k
        : context_(contextIn), authority_(authorityName) {}
4094
4095
91.1k
    inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
4096
4097
113k
    inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
4098
113k
        return context_;
4099
113k
    }
4100
4101
    // cppcheck-suppress functionStatic
4102
40.2k
    void setThis(AuthorityFactoryNNPtr factory) {
4103
40.2k
        thisFactory_ = factory.as_nullable();
4104
40.2k
    }
4105
4106
    // cppcheck-suppress functionStatic
4107
27
    AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
4108
4109
42.0k
    inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
4110
42.0k
        if (auth_name == authority_) {
4111
26.4k
            return NN_NO_CHECK(thisFactory_.lock());
4112
26.4k
        }
4113
15.6k
        return AuthorityFactory::create(context_, auth_name);
4114
42.0k
    }
4115
4116
    bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
4117
                                  bool considerKnownGridsAsAvailable);
4118
4119
    UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
4120
                                      const std::string &code);
4121
4122
    util::PropertyMap
4123
    createProperties(const std::string &code, const std::string &name,
4124
                     bool deprecated,
4125
                     const std::vector<ObjectDomainNNPtr> &usages);
4126
4127
    util::PropertyMap
4128
    createPropertiesSearchUsages(const std::string &table_name,
4129
                                 const std::string &code,
4130
                                 const std::string &name, bool deprecated);
4131
4132
    util::PropertyMap createPropertiesSearchUsages(
4133
        const std::string &table_name, const std::string &code,
4134
        const std::string &name, bool deprecated, const std::string &remarks);
4135
4136
    SQLResultSet run(const std::string &sql,
4137
                     const ListOfParams &parameters = ListOfParams());
4138
4139
    SQLResultSet runWithCodeParam(const std::string &sql,
4140
                                  const std::string &code);
4141
4142
    SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
4143
4144
132k
    bool hasAuthorityRestriction() const {
4145
132k
        return !authority_.empty() && authority_ != "any";
4146
132k
    }
4147
4148
    SQLResultSet createProjectedCRSBegin(const std::string &code);
4149
    crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
4150
                                                 const SQLResultSet &res);
4151
4152
  private:
4153
    DatabaseContextNNPtr context_;
4154
    std::string authority_;
4155
    std::weak_ptr<AuthorityFactory> thisFactory_{};
4156
};
4157
4158
// ---------------------------------------------------------------------------
4159
4160
SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
4161
56.2k
                                            const ListOfParams &parameters) {
4162
56.2k
    return context()->getPrivate()->run(sql, parameters);
4163
56.2k
}
4164
4165
// ---------------------------------------------------------------------------
4166
4167
SQLResultSet
4168
AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
4169
15.8k
                                            const std::string &code) {
4170
15.8k
    return run(sql, {authority(), code});
4171
15.8k
}
4172
4173
// ---------------------------------------------------------------------------
4174
4175
SQLResultSet
4176
AuthorityFactory::Private::runWithCodeParam(const char *sql,
4177
12.4k
                                            const std::string &code) {
4178
12.4k
    return runWithCodeParam(std::string(sql), code);
4179
12.4k
}
4180
4181
// ---------------------------------------------------------------------------
4182
4183
UnitOfMeasure
4184
AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
4185
12.4k
                                               const std::string &code) {
4186
12.4k
    return *(createFactory(auth_name)->createUnitOfMeasure(code));
4187
12.4k
}
4188
4189
// ---------------------------------------------------------------------------
4190
4191
util::PropertyMap AuthorityFactory::Private::createProperties(
4192
    const std::string &code, const std::string &name, bool deprecated,
4193
10.9k
    const std::vector<ObjectDomainNNPtr> &usages) {
4194
10.9k
    auto props = util::PropertyMap()
4195
10.9k
                     .set(metadata::Identifier::CODESPACE_KEY, authority())
4196
10.9k
                     .set(metadata::Identifier::CODE_KEY, code)
4197
10.9k
                     .set(common::IdentifiedObject::NAME_KEY, name);
4198
10.9k
    if (deprecated) {
4199
296
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
4200
296
    }
4201
10.9k
    if (!usages.empty()) {
4202
4203
10.3k
        auto array(util::ArrayOfBaseObject::create());
4204
10.4k
        for (const auto &usage : usages) {
4205
10.4k
            array->add(usage);
4206
10.4k
        }
4207
10.3k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
4208
10.3k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
4209
10.3k
    }
4210
10.9k
    return props;
4211
10.9k
}
4212
4213
// ---------------------------------------------------------------------------
4214
4215
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4216
    const std::string &table_name, const std::string &code,
4217
10.8k
    const std::string &name, bool deprecated) {
4218
4219
10.8k
    SQLResultSet res;
4220
10.8k
    if (table_name == "geodetic_crs" && code == "4326" &&
4221
10.8k
        authority() == "EPSG") {
4222
        // EPSG v10.077 has changed the extent from 1262 to 2830, whose
4223
        // description is super verbose.
4224
        // Cf https://epsg.org/closed-change-request/browse/id/2022.086
4225
        // To avoid churn in our WKT2 output, hot patch to the usage of
4226
        // 10.076 and earlier
4227
9
        res = run("SELECT extent.description, extent.south_lat, "
4228
9
                  "extent.north_lat, extent.west_lon, extent.east_lon, "
4229
9
                  "scope.scope, 0 AS score FROM extent, scope WHERE "
4230
9
                  "extent.code = 1262 and scope.code = 1183");
4231
10.8k
    } else {
4232
10.8k
        const std::string sql(
4233
10.8k
            "SELECT extent.description, extent.south_lat, "
4234
10.8k
            "extent.north_lat, extent.west_lon, extent.east_lon, "
4235
10.8k
            "scope.scope, "
4236
10.8k
            "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
4237
10.8k
            "AS score "
4238
10.8k
            "FROM usage "
4239
10.8k
            "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
4240
10.8k
            "usage.extent_code = extent.code "
4241
10.8k
            "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
4242
10.8k
            "usage.scope_code = scope.code "
4243
10.8k
            "WHERE object_table_name = ? AND object_auth_name = ? AND "
4244
10.8k
            "object_code = ? AND "
4245
            // We voluntary exclude extent and scope with a specific code
4246
10.8k
            "NOT (usage.extent_auth_name = 'PROJ' AND "
4247
10.8k
            "usage.extent_code = 'EXTENT_UNKNOWN') AND "
4248
10.8k
            "NOT (usage.scope_auth_name = 'PROJ' AND "
4249
10.8k
            "usage.scope_code = 'SCOPE_UNKNOWN') "
4250
10.8k
            "ORDER BY score, usage.auth_name, usage.code");
4251
10.8k
        res = run(sql, {table_name, authority(), code});
4252
10.8k
    }
4253
10.8k
    std::vector<ObjectDomainNNPtr> usages;
4254
10.8k
    for (const auto &row : res) {
4255
10.4k
        try {
4256
10.4k
            size_t idx = 0;
4257
10.4k
            const auto &extent_description = row[idx++];
4258
10.4k
            const auto &south_lat_str = row[idx++];
4259
10.4k
            const auto &north_lat_str = row[idx++];
4260
10.4k
            const auto &west_lon_str = row[idx++];
4261
10.4k
            const auto &east_lon_str = row[idx++];
4262
10.4k
            const auto &scope = row[idx];
4263
4264
10.4k
            util::optional<std::string> scopeOpt;
4265
10.4k
            if (!scope.empty()) {
4266
10.4k
                scopeOpt = scope;
4267
10.4k
            }
4268
4269
10.4k
            metadata::ExtentPtr extent;
4270
10.4k
            if (south_lat_str.empty()) {
4271
0
                extent = metadata::Extent::create(
4272
0
                             util::optional<std::string>(extent_description),
4273
0
                             {}, {}, {})
4274
0
                             .as_nullable();
4275
10.4k
            } else {
4276
10.4k
                double south_lat = c_locale_stod(south_lat_str);
4277
10.4k
                double north_lat = c_locale_stod(north_lat_str);
4278
10.4k
                double west_lon = c_locale_stod(west_lon_str);
4279
10.4k
                double east_lon = c_locale_stod(east_lon_str);
4280
10.4k
                auto bbox = metadata::GeographicBoundingBox::create(
4281
10.4k
                    west_lon, south_lat, east_lon, north_lat);
4282
10.4k
                extent = metadata::Extent::create(
4283
10.4k
                             util::optional<std::string>(extent_description),
4284
10.4k
                             std::vector<metadata::GeographicExtentNNPtr>{bbox},
4285
10.4k
                             std::vector<metadata::VerticalExtentNNPtr>(),
4286
10.4k
                             std::vector<metadata::TemporalExtentNNPtr>())
4287
10.4k
                             .as_nullable();
4288
10.4k
            }
4289
4290
10.4k
            usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
4291
10.4k
        } catch (const std::exception &) {
4292
0
        }
4293
10.4k
    }
4294
10.8k
    return createProperties(code, name, deprecated, std::move(usages));
4295
10.8k
}
4296
4297
// ---------------------------------------------------------------------------
4298
4299
util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
4300
    const std::string &table_name, const std::string &code,
4301
4.29k
    const std::string &name, bool deprecated, const std::string &remarks) {
4302
4.29k
    auto props =
4303
4.29k
        createPropertiesSearchUsages(table_name, code, name, deprecated);
4304
4.29k
    if (!remarks.empty())
4305
1.03k
        props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
4306
4.29k
    return props;
4307
4.29k
}
4308
4309
// ---------------------------------------------------------------------------
4310
4311
bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
4312
    const operation::CoordinateOperationNNPtr &op,
4313
0
    bool considerKnownGridsAsAvailable) {
4314
4315
0
    struct DisableNetwork {
4316
0
        const DatabaseContextNNPtr &m_dbContext;
4317
0
        bool m_old_network_enabled = false;
4318
4319
0
        explicit DisableNetwork(const DatabaseContextNNPtr &l_context)
4320
0
            : m_dbContext(l_context) {
4321
0
            auto ctxt = m_dbContext->d->pjCtxt();
4322
0
            if (ctxt == nullptr) {
4323
0
                ctxt = pj_get_default_ctx();
4324
0
                m_dbContext->d->setPjCtxt(ctxt);
4325
0
            }
4326
0
            m_old_network_enabled =
4327
0
                proj_context_is_network_enabled(ctxt) != FALSE;
4328
0
            if (m_old_network_enabled)
4329
0
                proj_context_set_enable_network(ctxt, false);
4330
0
        }
4331
4332
0
        ~DisableNetwork() {
4333
0
            if (m_old_network_enabled) {
4334
0
                auto ctxt = m_dbContext->d->pjCtxt();
4335
0
                proj_context_set_enable_network(ctxt, true);
4336
0
            }
4337
0
        }
4338
0
    };
4339
4340
0
    auto &l_context = context();
4341
    // Temporarily disable networking as we are only interested in known grids
4342
0
    DisableNetwork disabler(l_context);
4343
4344
0
    for (const auto &gridDesc :
4345
0
         op->gridsNeeded(l_context, considerKnownGridsAsAvailable)) {
4346
0
        if (!gridDesc.available) {
4347
0
            return true;
4348
0
        }
4349
0
    }
4350
0
    return false;
4351
0
}
4352
4353
//! @endcond
4354
4355
// ---------------------------------------------------------------------------
4356
4357
//! @cond Doxygen_Suppress
4358
40.2k
AuthorityFactory::~AuthorityFactory() = default;
4359
//! @endcond
4360
4361
// ---------------------------------------------------------------------------
4362
4363
AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
4364
                                   const std::string &authorityName)
4365
40.2k
    : d(std::make_unique<Private>(context, authorityName)) {}
4366
4367
// ---------------------------------------------------------------------------
4368
4369
// clang-format off
4370
/** \brief Instantiate a AuthorityFactory.
4371
 *
4372
 * The authority name might be set to the empty string in the particular case
4373
 * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
4374
 * is called.
4375
 *
4376
 * @param context Context.
4377
 * @param authorityName Authority name.
4378
 * @return new AuthorityFactory.
4379
 */
4380
// clang-format on
4381
4382
AuthorityFactoryNNPtr
4383
AuthorityFactory::create(const DatabaseContextNNPtr &context,
4384
40.2k
                         const std::string &authorityName) {
4385
40.2k
    const auto getFactory = [&context, &authorityName]() {
4386
40.2k
        for (const auto &knownName :
4387
90.5k
             {metadata::Identifier::EPSG.c_str(), "ESRI", "PROJ"}) {
4388
90.5k
            if (ci_equal(authorityName, knownName)) {
4389
16.0k
                return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4390
16.0k
                    context, knownName);
4391
16.0k
            }
4392
90.5k
        }
4393
24.1k
        return AuthorityFactory::nn_make_shared<AuthorityFactory>(
4394
24.1k
            context, authorityName);
4395
40.2k
    };
4396
40.2k
    auto factory = getFactory();
4397
40.2k
    factory->d->setThis(factory);
4398
40.2k
    return factory;
4399
40.2k
}
4400
4401
// ---------------------------------------------------------------------------
4402
4403
/** \brief Returns the database context. */
4404
837
const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
4405
837
    return d->context();
4406
837
}
4407
4408
// ---------------------------------------------------------------------------
4409
4410
//! @cond Doxygen_Suppress
4411
AuthorityFactory::CRSInfo::CRSInfo()
4412
0
    : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
4413
0
      bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
4414
0
      north_lat_degree{}, areaName{}, projectionMethodName{},
4415
0
      celestialBodyName{} {}
4416
//! @endcond
4417
4418
// ---------------------------------------------------------------------------
4419
4420
/** \brief Returns an arbitrary object from a code.
4421
 *
4422
 * The returned object will typically be an instance of Datum,
4423
 * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
4424
 * the object is know at compile time, it is recommended to invoke the most
4425
 * precise method instead of this one (for example
4426
 * createCoordinateReferenceSystem(code) instead of createObject(code)
4427
 * if the caller know he is asking for a coordinate reference system).
4428
 *
4429
 * If there are several objects with the same code, a FactoryException is
4430
 * thrown.
4431
 *
4432
 * @param code Object code allocated by authority. (e.g. "4326")
4433
 * @return object.
4434
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4435
 * @throw FactoryException in case of other errors.
4436
 */
4437
4438
util::BaseObjectNNPtr
4439
0
AuthorityFactory::createObject(const std::string &code) const {
4440
4441
0
    auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
4442
0
                                   "WHERE auth_name = ? AND code = ?",
4443
0
                                   code);
4444
0
    if (res.empty()) {
4445
0
        throw NoSuchAuthorityCodeException("not found", d->authority(), code);
4446
0
    }
4447
0
    if (res.size() != 1) {
4448
0
        std::string msg(
4449
0
            "More than one object matching specified code. Objects found in ");
4450
0
        bool first = true;
4451
0
        for (const auto &row : res) {
4452
0
            if (!first)
4453
0
                msg += ", ";
4454
0
            msg += row[0];
4455
0
            first = false;
4456
0
        }
4457
0
        throw FactoryException(msg);
4458
0
    }
4459
0
    const auto &first_row = res.front();
4460
0
    const auto &table_name = first_row[0];
4461
0
    const auto &type = first_row[1];
4462
0
    if (table_name == "extent") {
4463
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4464
0
            createExtent(code));
4465
0
    }
4466
0
    if (table_name == "unit_of_measure") {
4467
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4468
0
            createUnitOfMeasure(code));
4469
0
    }
4470
0
    if (table_name == "prime_meridian") {
4471
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4472
0
            createPrimeMeridian(code));
4473
0
    }
4474
0
    if (table_name == "ellipsoid") {
4475
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4476
0
            createEllipsoid(code));
4477
0
    }
4478
0
    if (table_name == "geodetic_datum") {
4479
0
        if (type == "ensemble") {
4480
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4481
0
                createDatumEnsemble(code, table_name));
4482
0
        }
4483
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4484
0
            createGeodeticDatum(code));
4485
0
    }
4486
0
    if (table_name == "vertical_datum") {
4487
0
        if (type == "ensemble") {
4488
0
            return util::nn_static_pointer_cast<util::BaseObject>(
4489
0
                createDatumEnsemble(code, table_name));
4490
0
        }
4491
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4492
0
            createVerticalDatum(code));
4493
0
    }
4494
0
    if (table_name == "engineering_datum") {
4495
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4496
0
            createEngineeringDatum(code));
4497
0
    }
4498
0
    if (table_name == "geodetic_crs") {
4499
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4500
0
            createGeodeticCRS(code));
4501
0
    }
4502
0
    if (table_name == "vertical_crs") {
4503
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4504
0
            createVerticalCRS(code));
4505
0
    }
4506
0
    if (table_name == "projected_crs") {
4507
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4508
0
            createProjectedCRS(code));
4509
0
    }
4510
0
    if (table_name == "compound_crs") {
4511
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4512
0
            createCompoundCRS(code));
4513
0
    }
4514
0
    if (table_name == "engineering_crs") {
4515
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4516
0
            createEngineeringCRS(code));
4517
0
    }
4518
0
    if (table_name == "conversion") {
4519
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4520
0
            createConversion(code));
4521
0
    }
4522
0
    if (table_name == "helmert_transformation" ||
4523
0
        table_name == "grid_transformation" ||
4524
0
        table_name == "other_transformation" ||
4525
0
        table_name == "concatenated_operation") {
4526
0
        return util::nn_static_pointer_cast<util::BaseObject>(
4527
0
            createCoordinateOperation(code, false));
4528
0
    }
4529
0
    throw FactoryException("unimplemented factory for " + res.front()[0]);
4530
0
}
4531
4532
// ---------------------------------------------------------------------------
4533
4534
//! @cond Doxygen_Suppress
4535
static FactoryException buildFactoryException(const char *type,
4536
                                              const std::string &authName,
4537
                                              const std::string &code,
4538
0
                                              const std::exception &ex) {
4539
0
    return FactoryException(std::string("cannot build ") + type + " " +
4540
0
                            authName + ":" + code + ": " + ex.what());
4541
0
}
4542
//! @endcond
4543
4544
// ---------------------------------------------------------------------------
4545
4546
/** \brief Returns a metadata::Extent from the specified code.
4547
 *
4548
 * @param code Object code allocated by authority.
4549
 * @return object.
4550
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4551
 * @throw FactoryException in case of other errors.
4552
 */
4553
4554
metadata::ExtentNNPtr
4555
0
AuthorityFactory::createExtent(const std::string &code) const {
4556
0
    const auto cacheKey(d->authority() + code);
4557
0
    {
4558
0
        auto extent = d->context()->d->getExtentFromCache(cacheKey);
4559
0
        if (extent) {
4560
0
            return NN_NO_CHECK(extent);
4561
0
        }
4562
0
    }
4563
0
    auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
4564
0
               "deprecated FROM extent WHERE auth_name = ? AND code = ?";
4565
0
    auto res = d->runWithCodeParam(sql, code);
4566
0
    if (res.empty()) {
4567
0
        throw NoSuchAuthorityCodeException("extent not found", d->authority(),
4568
0
                                           code);
4569
0
    }
4570
0
    try {
4571
0
        const auto &row = res.front();
4572
0
        const auto &description = row[0];
4573
0
        if (row[1].empty()) {
4574
0
            auto extent = metadata::Extent::create(
4575
0
                util::optional<std::string>(description), {}, {}, {});
4576
0
            d->context()->d->cache(cacheKey, extent);
4577
0
            return extent;
4578
0
        }
4579
0
        double south_lat = c_locale_stod(row[1]);
4580
0
        double north_lat = c_locale_stod(row[2]);
4581
0
        double west_lon = c_locale_stod(row[3]);
4582
0
        double east_lon = c_locale_stod(row[4]);
4583
0
        auto bbox = metadata::GeographicBoundingBox::create(
4584
0
            west_lon, south_lat, east_lon, north_lat);
4585
4586
0
        auto extent = metadata::Extent::create(
4587
0
            util::optional<std::string>(description),
4588
0
            std::vector<metadata::GeographicExtentNNPtr>{bbox},
4589
0
            std::vector<metadata::VerticalExtentNNPtr>(),
4590
0
            std::vector<metadata::TemporalExtentNNPtr>());
4591
0
        d->context()->d->cache(cacheKey, extent);
4592
0
        return extent;
4593
4594
0
    } catch (const std::exception &ex) {
4595
0
        throw buildFactoryException("extent", d->authority(), code, ex);
4596
0
    }
4597
0
}
4598
4599
// ---------------------------------------------------------------------------
4600
4601
/** \brief Returns a common::UnitOfMeasure from the specified code.
4602
 *
4603
 * @param code Object code allocated by authority.
4604
 * @return object.
4605
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4606
 * @throw FactoryException in case of other errors.
4607
 */
4608
4609
UnitOfMeasureNNPtr
4610
12.4k
AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
4611
12.4k
    const auto cacheKey(d->authority() + code);
4612
12.4k
    {
4613
12.4k
        auto uom = d->context()->d->getUOMFromCache(cacheKey);
4614
12.4k
        if (uom) {
4615
12.3k
            return NN_NO_CHECK(uom);
4616
12.3k
        }
4617
12.4k
    }
4618
82
    auto res = d->context()->d->run(
4619
82
        "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
4620
82
        "auth_name = ? AND code = ?",
4621
82
        {d->authority(), code}, true);
4622
82
    if (res.empty()) {
4623
0
        throw NoSuchAuthorityCodeException("unit of measure not found",
4624
0
                                           d->authority(), code);
4625
0
    }
4626
82
    try {
4627
82
        const auto &row = res.front();
4628
82
        const auto &name =
4629
82
            (row[0] == "degree (supplier to define representation)")
4630
82
                ? UnitOfMeasure::DEGREE.name()
4631
82
                : row[0];
4632
82
        double conv_factor = (code == "9107" || code == "9108")
4633
82
                                 ? UnitOfMeasure::DEGREE.conversionToSI()
4634
82
                                 : c_locale_stod(row[1]);
4635
82
        constexpr double EPS = 1e-10;
4636
82
        if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
4637
82
            EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
4638
27
            conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
4639
27
        }
4640
82
        if (std::fabs(conv_factor -
4641
82
                      UnitOfMeasure::ARC_SECOND.conversionToSI()) <
4642
82
            EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
4643
1
            conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
4644
1
        }
4645
82
        const auto &type_str = row[2];
4646
82
        UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
4647
82
        if (type_str == "length")
4648
35
            unitType = UnitOfMeasure::Type::LINEAR;
4649
47
        else if (type_str == "angle")
4650
34
            unitType = UnitOfMeasure::Type::ANGULAR;
4651
13
        else if (type_str == "scale")
4652
12
            unitType = UnitOfMeasure::Type::SCALE;
4653
1
        else if (type_str == "time")
4654
1
            unitType = UnitOfMeasure::Type::TIME;
4655
82
        auto uom = util::nn_make_shared<UnitOfMeasure>(
4656
82
            name, conv_factor, unitType, d->authority(), code);
4657
82
        d->context()->d->cache(cacheKey, uom);
4658
82
        return uom;
4659
82
    } catch (const std::exception &ex) {
4660
0
        throw buildFactoryException("unit of measure", d->authority(), code,
4661
0
                                    ex);
4662
0
    }
4663
82
}
4664
4665
// ---------------------------------------------------------------------------
4666
4667
//! @cond Doxygen_Suppress
4668
static double normalizeMeasure(const std::string &uom_code,
4669
                               const std::string &value,
4670
10.0k
                               std::string &normalized_uom_code) {
4671
10.0k
    if (uom_code == "9110") // DDD.MMSSsss.....
4672
1.85k
    {
4673
1.85k
        double normalized_value = c_locale_stod(value);
4674
1.85k
        std::ostringstream buffer;
4675
1.85k
        buffer.imbue(std::locale::classic());
4676
1.85k
        constexpr size_t precision = 12;
4677
1.85k
        buffer << std::fixed << std::setprecision(precision)
4678
1.85k
               << normalized_value;
4679
1.85k
        auto formatted = buffer.str();
4680
1.85k
        size_t dotPos = formatted.find('.');
4681
1.85k
        assert(dotPos + 1 + precision == formatted.size());
4682
1.85k
        auto minutes = formatted.substr(dotPos + 1, 2);
4683
1.85k
        auto seconds = formatted.substr(dotPos + 3);
4684
1.85k
        assert(seconds.size() == precision - 2);
4685
1.85k
        normalized_value =
4686
1.85k
            (normalized_value < 0 ? -1.0 : 1.0) *
4687
1.85k
            (std::floor(std::fabs(normalized_value)) +
4688
1.85k
             c_locale_stod(minutes) / 60. +
4689
1.85k
             (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
4690
1.85k
                 3600.);
4691
1.85k
        normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
4692
        /* coverity[overflow_sink] */
4693
1.85k
        return normalized_value;
4694
8.15k
    } else {
4695
8.15k
        normalized_uom_code = uom_code;
4696
8.15k
        return c_locale_stod(value);
4697
8.15k
    }
4698
10.0k
}
4699
//! @endcond
4700
4701
// ---------------------------------------------------------------------------
4702
4703
/** \brief Returns a datum::PrimeMeridian from the specified code.
4704
 *
4705
 * @param code Object code allocated by authority.
4706
 * @return object.
4707
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4708
 * @throw FactoryException in case of other errors.
4709
 */
4710
4711
datum::PrimeMeridianNNPtr
4712
1.30k
AuthorityFactory::createPrimeMeridian(const std::string &code) const {
4713
1.30k
    const auto cacheKey(d->authority() + code);
4714
1.30k
    {
4715
1.30k
        auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
4716
1.30k
        if (pm) {
4717
1.26k
            return NN_NO_CHECK(pm);
4718
1.26k
        }
4719
1.30k
    }
4720
42
    auto res = d->runWithCodeParam(
4721
42
        "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
4722
42
        "prime_meridian WHERE "
4723
42
        "auth_name = ? AND code = ?",
4724
42
        code);
4725
42
    if (res.empty()) {
4726
0
        throw NoSuchAuthorityCodeException("prime meridian not found",
4727
0
                                           d->authority(), code);
4728
0
    }
4729
42
    try {
4730
42
        const auto &row = res.front();
4731
42
        const auto &name = row[0];
4732
42
        const auto &longitude = row[1];
4733
42
        const auto &uom_auth_name = row[2];
4734
42
        const auto &uom_code = row[3];
4735
42
        const bool deprecated = row[4] == "1";
4736
4737
42
        std::string normalized_uom_code(uom_code);
4738
42
        const double normalized_value =
4739
42
            normalizeMeasure(uom_code, longitude, normalized_uom_code);
4740
4741
42
        auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
4742
42
        auto props = d->createProperties(code, name, deprecated, {});
4743
42
        auto pm = datum::PrimeMeridian::create(
4744
42
            props, common::Angle(normalized_value, uom));
4745
42
        d->context()->d->cache(cacheKey, pm);
4746
42
        return pm;
4747
42
    } catch (const std::exception &ex) {
4748
0
        throw buildFactoryException("prime meridian", d->authority(), code, ex);
4749
0
    }
4750
42
}
4751
4752
// ---------------------------------------------------------------------------
4753
4754
/** \brief Identify a celestial body from an approximate radius.
4755
 *
4756
 * @param semi_major_axis Approximate semi-major axis.
4757
 * @param tolerance Relative error allowed.
4758
 * @return celestial body name if one single match found.
4759
 * @throw FactoryException in case of error.
4760
 */
4761
4762
std::string
4763
AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
4764
9.54k
                                                double tolerance) const {
4765
9.54k
    auto res =
4766
9.54k
        d->run("SELECT DISTINCT name, "
4767
9.54k
               "(ABS(semi_major_axis - ?) / semi_major_axis ) AS rel_error "
4768
9.54k
               "FROM celestial_body WHERE rel_error <= ? "
4769
9.54k
               "ORDER BY rel_error, name",
4770
9.54k
               {semi_major_axis, tolerance});
4771
9.54k
    if (res.empty()) {
4772
9.28k
        throw FactoryException("no match found");
4773
9.28k
    }
4774
264
    constexpr int IDX_NAME = 0;
4775
264
    if (res.size() > 1) {
4776
219
        constexpr int IDX_REL_ERROR = 1;
4777
        // If the first object has a relative error of 0 and the next one
4778
        // a non-zero error, then use the first one.
4779
219
        if (res.front()[IDX_REL_ERROR] == "0" &&
4780
219
            (*std::next(res.begin()))[IDX_REL_ERROR] != "0") {
4781
0
            return res.front()[IDX_NAME];
4782
0
        }
4783
438
        for (const auto &row : res) {
4784
438
            if (row[IDX_NAME] != res.front()[IDX_NAME]) {
4785
37
                throw FactoryException("more than one match found");
4786
37
            }
4787
438
        }
4788
219
    }
4789
227
    return res.front()[IDX_NAME];
4790
264
}
4791
4792
// ---------------------------------------------------------------------------
4793
4794
/** \brief Returns a datum::Ellipsoid from the specified code.
4795
 *
4796
 * @param code Object code allocated by authority.
4797
 * @return object.
4798
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4799
 * @throw FactoryException in case of other errors.
4800
 */
4801
4802
datum::EllipsoidNNPtr
4803
1.37k
AuthorityFactory::createEllipsoid(const std::string &code) const {
4804
1.37k
    const auto cacheKey(d->authority() + code);
4805
1.37k
    {
4806
1.37k
        auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
4807
1.37k
        if (ellps) {
4808
1.24k
            return NN_NO_CHECK(ellps);
4809
1.24k
        }
4810
1.37k
    }
4811
132
    auto res = d->runWithCodeParam(
4812
132
        "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
4813
132
        "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
4814
132
        "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
4815
132
        "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
4816
132
        "ellipsoid JOIN celestial_body "
4817
132
        "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
4818
132
        "ellipsoid.celestial_body_code = celestial_body.code WHERE "
4819
132
        "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
4820
132
        code);
4821
132
    if (res.empty()) {
4822
0
        throw NoSuchAuthorityCodeException("ellipsoid not found",
4823
0
                                           d->authority(), code);
4824
0
    }
4825
132
    try {
4826
132
        const auto &row = res.front();
4827
132
        const auto &name = row[0];
4828
132
        const auto &semi_major_axis_str = row[1];
4829
132
        double semi_major_axis = c_locale_stod(semi_major_axis_str);
4830
132
        const auto &uom_auth_name = row[2];
4831
132
        const auto &uom_code = row[3];
4832
132
        const auto &inv_flattening_str = row[4];
4833
132
        const auto &semi_minor_axis_str = row[5];
4834
132
        const auto &body = row[6];
4835
132
        const bool deprecated = row[7] == "1";
4836
132
        auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
4837
132
        auto props = d->createProperties(code, name, deprecated, {});
4838
132
        if (!inv_flattening_str.empty()) {
4839
97
            auto ellps = datum::Ellipsoid::createFlattenedSphere(
4840
97
                props, common::Length(semi_major_axis, uom),
4841
97
                common::Scale(c_locale_stod(inv_flattening_str)), body);
4842
97
            d->context()->d->cache(cacheKey, ellps);
4843
97
            return ellps;
4844
97
        } else if (semi_major_axis_str == semi_minor_axis_str) {
4845
14
            auto ellps = datum::Ellipsoid::createSphere(
4846
14
                props, common::Length(semi_major_axis, uom), body);
4847
14
            d->context()->d->cache(cacheKey, ellps);
4848
14
            return ellps;
4849
21
        } else {
4850
21
            auto ellps = datum::Ellipsoid::createTwoAxis(
4851
21
                props, common::Length(semi_major_axis, uom),
4852
21
                common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
4853
21
            d->context()->d->cache(cacheKey, ellps);
4854
21
            return ellps;
4855
21
        }
4856
132
    } catch (const std::exception &ex) {
4857
0
        throw buildFactoryException("ellipsoid", d->authority(), code, ex);
4858
0
    }
4859
132
}
4860
4861
// ---------------------------------------------------------------------------
4862
4863
/** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
4864
 *
4865
 * @param code Object code allocated by authority.
4866
 * @return object.
4867
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4868
 * @throw FactoryException in case of other errors.
4869
 */
4870
4871
datum::GeodeticReferenceFrameNNPtr
4872
879
AuthorityFactory::createGeodeticDatum(const std::string &code) const {
4873
4874
879
    datum::GeodeticReferenceFramePtr datum;
4875
879
    datum::DatumEnsemblePtr datumEnsemble;
4876
879
    constexpr bool turnEnsembleAsDatum = true;
4877
879
    createGeodeticDatumOrEnsemble(code, datum, datumEnsemble,
4878
879
                                  turnEnsembleAsDatum);
4879
879
    return NN_NO_CHECK(datum);
4880
879
}
4881
4882
// ---------------------------------------------------------------------------
4883
4884
void AuthorityFactory::createGeodeticDatumOrEnsemble(
4885
    const std::string &code, datum::GeodeticReferenceFramePtr &outDatum,
4886
4.03k
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
4887
4.03k
    const auto cacheKey(d->authority() + code);
4888
4.03k
    {
4889
4.03k
        outDatumEnsemble = d->context()->d->getDatumEnsembleFromCache(cacheKey);
4890
4.03k
        if (outDatumEnsemble) {
4891
310
            if (!turnEnsembleAsDatum)
4892
93
                return;
4893
217
            outDatumEnsemble = nullptr;
4894
217
        }
4895
3.94k
        outDatum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
4896
3.94k
        if (outDatum) {
4897
2.63k
            return;
4898
2.63k
        }
4899
3.94k
    }
4900
1.31k
    auto res = d->runWithCodeParam(
4901
1.31k
        "SELECT name, ellipsoid_auth_name, ellipsoid_code, "
4902
1.31k
        "prime_meridian_auth_name, prime_meridian_code, "
4903
1.31k
        "publication_date, frame_reference_epoch, "
4904
1.31k
        "ensemble_accuracy, anchor, anchor_epoch, deprecated "
4905
1.31k
        "FROM geodetic_datum "
4906
1.31k
        "WHERE "
4907
1.31k
        "auth_name = ? AND code = ?",
4908
1.31k
        code);
4909
1.31k
    if (res.empty()) {
4910
0
        throw NoSuchAuthorityCodeException("geodetic datum not found",
4911
0
                                           d->authority(), code);
4912
0
    }
4913
1.31k
    try {
4914
1.31k
        const auto &row = res.front();
4915
1.31k
        const auto &name = row[0];
4916
1.31k
        const auto &ellipsoid_auth_name = row[1];
4917
1.31k
        const auto &ellipsoid_code = row[2];
4918
1.31k
        const auto &prime_meridian_auth_name = row[3];
4919
1.31k
        const auto &prime_meridian_code = row[4];
4920
1.31k
        const auto &publication_date = row[5];
4921
1.31k
        const auto &frame_reference_epoch = row[6];
4922
1.31k
        const auto &ensemble_accuracy = row[7];
4923
1.31k
        const auto &anchor = row[8];
4924
1.31k
        const auto &anchor_epoch = row[9];
4925
1.31k
        const bool deprecated = row[10] == "1";
4926
4927
1.31k
        std::string massagedName = name;
4928
1.31k
        if (turnEnsembleAsDatum) {
4929
72
            if (name == "World Geodetic System 1984 ensemble") {
4930
3
                massagedName = "World Geodetic System 1984";
4931
69
            } else if (name ==
4932
69
                       "European Terrestrial Reference System 1989 ensemble") {
4933
2
                massagedName = "European Terrestrial Reference System 1989";
4934
2
            }
4935
72
        }
4936
1.31k
        auto props = d->createPropertiesSearchUsages("geodetic_datum", code,
4937
1.31k
                                                     massagedName, deprecated);
4938
4939
1.31k
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
4940
2
            auto resMembers =
4941
2
                d->run("SELECT member_auth_name, member_code FROM "
4942
2
                       "geodetic_datum_ensemble_member WHERE "
4943
2
                       "ensemble_auth_name = ? AND ensemble_code = ? "
4944
2
                       "ORDER BY sequence",
4945
2
                       {d->authority(), code});
4946
4947
2
            std::vector<datum::DatumNNPtr> members;
4948
20
            for (const auto &memberRow : resMembers) {
4949
20
                members.push_back(
4950
20
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
4951
20
            }
4952
2
            auto datumEnsemble = datum::DatumEnsemble::create(
4953
2
                props, std::move(members),
4954
2
                metadata::PositionalAccuracy::create(ensemble_accuracy));
4955
2
            d->context()->d->cache(cacheKey, datumEnsemble);
4956
2
            outDatumEnsemble = datumEnsemble.as_nullable();
4957
1.30k
        } else {
4958
1.30k
            auto ellipsoid = d->createFactory(ellipsoid_auth_name)
4959
1.30k
                                 ->createEllipsoid(ellipsoid_code);
4960
1.30k
            auto pm = d->createFactory(prime_meridian_auth_name)
4961
1.30k
                          ->createPrimeMeridian(prime_meridian_code);
4962
4963
1.30k
            auto anchorOpt = util::optional<std::string>();
4964
1.30k
            if (!anchor.empty())
4965
17
                anchorOpt = anchor;
4966
1.30k
            if (!publication_date.empty()) {
4967
986
                props.set("PUBLICATION_DATE", publication_date);
4968
986
            }
4969
1.30k
            if (!anchor_epoch.empty()) {
4970
42
                props.set("ANCHOR_EPOCH", anchor_epoch);
4971
42
            }
4972
1.30k
            auto datum = frame_reference_epoch.empty()
4973
1.30k
                             ? datum::GeodeticReferenceFrame::create(
4974
1.10k
                                   props, ellipsoid, anchorOpt, pm)
4975
1.30k
                             : util::nn_static_pointer_cast<
4976
205
                                   datum::GeodeticReferenceFrame>(
4977
205
                                   datum::DynamicGeodeticReferenceFrame::create(
4978
205
                                       props, ellipsoid, anchorOpt, pm,
4979
205
                                       common::Measure(
4980
205
                                           c_locale_stod(frame_reference_epoch),
4981
205
                                           common::UnitOfMeasure::YEAR),
4982
205
                                       util::optional<std::string>()));
4983
1.30k
            d->context()->d->cache(cacheKey, datum);
4984
1.30k
            outDatum = datum.as_nullable();
4985
1.30k
        }
4986
1.31k
    } catch (const std::exception &ex) {
4987
0
        throw buildFactoryException("geodetic reference frame", d->authority(),
4988
0
                                    code, ex);
4989
0
    }
4990
1.31k
}
4991
4992
// ---------------------------------------------------------------------------
4993
4994
/** \brief Returns a datum::VerticalReferenceFrame from the specified code.
4995
 *
4996
 * @param code Object code allocated by authority.
4997
 * @return object.
4998
 * @throw NoSuchAuthorityCodeException if there is no matching object.
4999
 * @throw FactoryException in case of other errors.
5000
 */
5001
5002
datum::VerticalReferenceFrameNNPtr
5003
37
AuthorityFactory::createVerticalDatum(const std::string &code) const {
5004
37
    datum::VerticalReferenceFramePtr datum;
5005
37
    datum::DatumEnsemblePtr datumEnsemble;
5006
37
    constexpr bool turnEnsembleAsDatum = true;
5007
37
    createVerticalDatumOrEnsemble(code, datum, datumEnsemble,
5008
37
                                  turnEnsembleAsDatum);
5009
37
    return NN_NO_CHECK(datum);
5010
37
}
5011
5012
// ---------------------------------------------------------------------------
5013
5014
void AuthorityFactory::createVerticalDatumOrEnsemble(
5015
    const std::string &code, datum::VerticalReferenceFramePtr &outDatum,
5016
661
    datum::DatumEnsemblePtr &outDatumEnsemble, bool turnEnsembleAsDatum) const {
5017
661
    auto res =
5018
661
        d->runWithCodeParam("SELECT name, publication_date, "
5019
661
                            "frame_reference_epoch, ensemble_accuracy, anchor, "
5020
661
                            "anchor_epoch, deprecated FROM "
5021
661
                            "vertical_datum WHERE auth_name = ? AND code = ?",
5022
661
                            code);
5023
661
    if (res.empty()) {
5024
0
        throw NoSuchAuthorityCodeException("vertical datum not found",
5025
0
                                           d->authority(), code);
5026
0
    }
5027
661
    try {
5028
661
        const auto &row = res.front();
5029
661
        const auto &name = row[0];
5030
661
        const auto &publication_date = row[1];
5031
661
        const auto &frame_reference_epoch = row[2];
5032
661
        const auto &ensemble_accuracy = row[3];
5033
661
        const auto &anchor = row[4];
5034
661
        const auto &anchor_epoch = row[5];
5035
661
        const bool deprecated = row[6] == "1";
5036
661
        auto props = d->createPropertiesSearchUsages("vertical_datum", code,
5037
661
                                                     name, deprecated);
5038
661
        if (!turnEnsembleAsDatum && !ensemble_accuracy.empty()) {
5039
11
            auto resMembers =
5040
11
                d->run("SELECT member_auth_name, member_code FROM "
5041
11
                       "vertical_datum_ensemble_member WHERE "
5042
11
                       "ensemble_auth_name = ? AND ensemble_code = ? "
5043
11
                       "ORDER BY sequence",
5044
11
                       {d->authority(), code});
5045
5046
11
            std::vector<datum::DatumNNPtr> members;
5047
37
            for (const auto &memberRow : resMembers) {
5048
37
                members.push_back(
5049
37
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5050
37
            }
5051
11
            auto datumEnsemble = datum::DatumEnsemble::create(
5052
11
                props, std::move(members),
5053
11
                metadata::PositionalAccuracy::create(ensemble_accuracy));
5054
11
            outDatumEnsemble = datumEnsemble.as_nullable();
5055
650
        } else {
5056
650
            if (!publication_date.empty()) {
5057
276
                props.set("PUBLICATION_DATE", publication_date);
5058
276
            }
5059
650
            if (!anchor_epoch.empty()) {
5060
16
                props.set("ANCHOR_EPOCH", anchor_epoch);
5061
16
            }
5062
650
            if (d->authority() == "ESRI" &&
5063
650
                starts_with(code, "from_geogdatum_")) {
5064
255
                props.set("VERT_DATUM_TYPE", "2002");
5065
255
            }
5066
650
            auto anchorOpt = util::optional<std::string>();
5067
650
            if (!anchor.empty())
5068
0
                anchorOpt = anchor;
5069
650
            if (frame_reference_epoch.empty()) {
5070
646
                outDatum =
5071
646
                    datum::VerticalReferenceFrame::create(props, anchorOpt)
5072
646
                        .as_nullable();
5073
646
            } else {
5074
4
                outDatum =
5075
4
                    datum::DynamicVerticalReferenceFrame::create(
5076
4
                        props, anchorOpt,
5077
4
                        util::optional<datum::RealizationMethod>(),
5078
4
                        common::Measure(c_locale_stod(frame_reference_epoch),
5079
4
                                        common::UnitOfMeasure::YEAR),
5080
4
                        util::optional<std::string>())
5081
4
                        .as_nullable();
5082
4
            }
5083
650
        }
5084
661
    } catch (const std::exception &ex) {
5085
0
        throw buildFactoryException("vertical reference frame", d->authority(),
5086
0
                                    code, ex);
5087
0
    }
5088
661
}
5089
5090
// ---------------------------------------------------------------------------
5091
5092
/** \brief Returns a datum::EngineeringDatum from the specified code.
5093
 *
5094
 * @param code Object code allocated by authority.
5095
 * @return object.
5096
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5097
 * @throw FactoryException in case of other errors.
5098
 * @since 9.6
5099
 */
5100
5101
datum::EngineeringDatumNNPtr
5102
10
AuthorityFactory::createEngineeringDatum(const std::string &code) const {
5103
10
    auto res = d->runWithCodeParam(
5104
10
        "SELECT name, publication_date, "
5105
10
        "anchor, anchor_epoch, deprecated FROM "
5106
10
        "engineering_datum WHERE auth_name = ? AND code = ?",
5107
10
        code);
5108
10
    if (res.empty()) {
5109
0
        throw NoSuchAuthorityCodeException("engineering datum not found",
5110
0
                                           d->authority(), code);
5111
0
    }
5112
10
    try {
5113
10
        const auto &row = res.front();
5114
10
        const auto &name = row[0];
5115
10
        const auto &publication_date = row[1];
5116
10
        const auto &anchor = row[2];
5117
10
        const auto &anchor_epoch = row[3];
5118
10
        const bool deprecated = row[4] == "1";
5119
10
        auto props = d->createPropertiesSearchUsages("engineering_datum", code,
5120
10
                                                     name, deprecated);
5121
5122
10
        if (!publication_date.empty()) {
5123
3
            props.set("PUBLICATION_DATE", publication_date);
5124
3
        }
5125
10
        if (!anchor_epoch.empty()) {
5126
0
            props.set("ANCHOR_EPOCH", anchor_epoch);
5127
0
        }
5128
10
        auto anchorOpt = util::optional<std::string>();
5129
10
        if (!anchor.empty())
5130
0
            anchorOpt = anchor;
5131
10
        return datum::EngineeringDatum::create(props, anchorOpt);
5132
10
    } catch (const std::exception &ex) {
5133
0
        throw buildFactoryException("engineering datum", d->authority(), code,
5134
0
                                    ex);
5135
0
    }
5136
10
}
5137
5138
// ---------------------------------------------------------------------------
5139
5140
/** \brief Returns a datum::DatumEnsemble from the specified code.
5141
 *
5142
 * @param code Object code allocated by authority.
5143
 * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
5144
 * @return object.
5145
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5146
 * @throw FactoryException in case of other errors.
5147
 */
5148
5149
datum::DatumEnsembleNNPtr
5150
AuthorityFactory::createDatumEnsemble(const std::string &code,
5151
0
                                      const std::string &type) const {
5152
0
    auto res = d->run(
5153
0
        "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
5154
0
        "geodetic_datum WHERE "
5155
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
5156
0
        "UNION ALL "
5157
0
        "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
5158
0
        "vertical_datum WHERE "
5159
0
        "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
5160
0
        {d->authority(), code, d->authority(), code});
5161
0
    if (res.empty()) {
5162
0
        throw NoSuchAuthorityCodeException("datum ensemble not found",
5163
0
                                           d->authority(), code);
5164
0
    }
5165
0
    for (const auto &row : res) {
5166
0
        const std::string &gotType = row[0];
5167
0
        const std::string &name = row[1];
5168
0
        const std::string &ensembleAccuracy = row[2];
5169
0
        const bool deprecated = row[3] == "1";
5170
0
        if (type.empty() || type == gotType) {
5171
0
            auto resMembers =
5172
0
                d->run("SELECT member_auth_name, member_code FROM " + gotType +
5173
0
                           "_ensemble_member WHERE "
5174
0
                           "ensemble_auth_name = ? AND ensemble_code = ? "
5175
0
                           "ORDER BY sequence",
5176
0
                       {d->authority(), code});
5177
5178
0
            std::vector<datum::DatumNNPtr> members;
5179
0
            for (const auto &memberRow : resMembers) {
5180
0
                members.push_back(
5181
0
                    d->createFactory(memberRow[0])->createDatum(memberRow[1]));
5182
0
            }
5183
0
            auto props = d->createPropertiesSearchUsages(gotType, code, name,
5184
0
                                                         deprecated);
5185
0
            return datum::DatumEnsemble::create(
5186
0
                props, std::move(members),
5187
0
                metadata::PositionalAccuracy::create(ensembleAccuracy));
5188
0
        }
5189
0
    }
5190
0
    throw NoSuchAuthorityCodeException("datum ensemble not found",
5191
0
                                       d->authority(), code);
5192
0
}
5193
5194
// ---------------------------------------------------------------------------
5195
5196
/** \brief Returns a datum::Datum from the specified code.
5197
 *
5198
 * @param code Object code allocated by authority.
5199
 * @return object.
5200
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5201
 * @throw FactoryException in case of other errors.
5202
 */
5203
5204
65
datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
5205
65
    auto res = d->run(
5206
65
        "SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
5207
65
        "auth_name = ? AND code = ? "
5208
65
        "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
5209
65
        "auth_name = ? AND code = ? "
5210
65
        "UNION ALL SELECT 'engineering_datum' FROM engineering_datum "
5211
65
        "WHERE "
5212
65
        "auth_name = ? AND code = ?",
5213
65
        {d->authority(), code, d->authority(), code, d->authority(), code});
5214
65
    if (res.empty()) {
5215
8
        throw NoSuchAuthorityCodeException("datum not found", d->authority(),
5216
8
                                           code);
5217
8
    }
5218
57
    const auto &type = res.front()[0];
5219
57
    if (type == "geodetic_datum") {
5220
20
        return createGeodeticDatum(code);
5221
20
    }
5222
37
    if (type == "vertical_datum") {
5223
37
        return createVerticalDatum(code);
5224
37
    }
5225
0
    return createEngineeringDatum(code);
5226
37
}
5227
5228
// ---------------------------------------------------------------------------
5229
5230
//! @cond Doxygen_Suppress
5231
32
static cs::MeridianPtr createMeridian(const std::string &val) {
5232
32
    try {
5233
32
        const std::string degW(std::string("\xC2\xB0") + "W");
5234
32
        if (ends_with(val, degW)) {
5235
16
            return cs::Meridian::create(common::Angle(
5236
16
                -c_locale_stod(val.substr(0, val.size() - degW.size()))));
5237
16
        }
5238
16
        const std::string degE(std::string("\xC2\xB0") + "E");
5239
16
        if (ends_with(val, degE)) {
5240
16
            return cs::Meridian::create(common::Angle(
5241
16
                c_locale_stod(val.substr(0, val.size() - degE.size()))));
5242
16
        }
5243
16
    } catch (const std::exception &) {
5244
0
    }
5245
0
    return nullptr;
5246
32
}
5247
//! @endcond
5248
5249
// ---------------------------------------------------------------------------
5250
5251
/** \brief Returns a cs::CoordinateSystem from the specified code.
5252
 *
5253
 * @param code Object code allocated by authority.
5254
 * @return object.
5255
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5256
 * @throw FactoryException in case of other errors.
5257
 */
5258
5259
cs::CoordinateSystemNNPtr
5260
5.14k
AuthorityFactory::createCoordinateSystem(const std::string &code) const {
5261
5.14k
    const auto cacheKey(d->authority() + code);
5262
5.14k
    {
5263
5.14k
        auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
5264
5.14k
        if (cs) {
5265
5.05k
            return NN_NO_CHECK(cs);
5266
5.05k
        }
5267
5.14k
    }
5268
84
    auto res = d->runWithCodeParam(
5269
84
        "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
5270
84
        "cs.type FROM "
5271
84
        "axis LEFT JOIN coordinate_system cs ON "
5272
84
        "axis.coordinate_system_auth_name = cs.auth_name AND "
5273
84
        "axis.coordinate_system_code = cs.code WHERE "
5274
84
        "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
5275
84
        "BY coordinate_system_order",
5276
84
        code);
5277
84
    if (res.empty()) {
5278
0
        throw NoSuchAuthorityCodeException("coordinate system not found",
5279
0
                                           d->authority(), code);
5280
0
    }
5281
5282
84
    const auto &csType = res.front()[5];
5283
84
    std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
5284
175
    for (const auto &row : res) {
5285
175
        const auto &name = row[0];
5286
175
        const auto &abbrev = row[1];
5287
175
        const auto &orientation = row[2];
5288
175
        const auto &uom_auth_name = row[3];
5289
175
        const auto &uom_code = row[4];
5290
175
        if (uom_auth_name.empty() && csType != CS_TYPE_ORDINAL) {
5291
0
            throw FactoryException("no unit of measure for an axis is only "
5292
0
                                   "supported for ordinatal CS");
5293
0
        }
5294
175
        auto uom = uom_auth_name.empty()
5295
175
                       ? common::UnitOfMeasure::NONE
5296
175
                       : d->createUnitOfMeasure(uom_auth_name, uom_code);
5297
175
        auto props =
5298
175
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
5299
175
        const cs::AxisDirection *direction =
5300
175
            cs::AxisDirection::valueOf(orientation);
5301
175
        cs::MeridianPtr meridian;
5302
175
        if (direction == nullptr) {
5303
32
            if (orientation == "Geocentre > equator/0"
5304
32
                               "\xC2\xB0"
5305
32
                               "E") {
5306
0
                direction = &(cs::AxisDirection::GEOCENTRIC_X);
5307
32
            } else if (orientation == "Geocentre > equator/90"
5308
32
                                      "\xC2\xB0"
5309
32
                                      "E") {
5310
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Y);
5311
32
            } else if (orientation == "Geocentre > north pole") {
5312
0
                direction = &(cs::AxisDirection::GEOCENTRIC_Z);
5313
32
            } else if (starts_with(orientation, "North along ")) {
5314
18
                direction = &(cs::AxisDirection::NORTH);
5315
18
                meridian =
5316
18
                    createMeridian(orientation.substr(strlen("North along ")));
5317
18
            } else if (starts_with(orientation, "South along ")) {
5318
14
                direction = &(cs::AxisDirection::SOUTH);
5319
14
                meridian =
5320
14
                    createMeridian(orientation.substr(strlen("South along ")));
5321
14
            } else {
5322
0
                throw FactoryException("unknown axis direction: " +
5323
0
                                       orientation);
5324
0
            }
5325
32
        }
5326
175
        axisList.emplace_back(cs::CoordinateSystemAxis::create(
5327
175
            props, abbrev, *direction, uom, meridian));
5328
175
    }
5329
5330
84
    const auto cacheAndRet = [this,
5331
84
                              &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
5332
84
        d->context()->d->cache(cacheKey, cs);
5333
84
        return cs;
5334
84
    };
5335
5336
84
    auto props = util::PropertyMap()
5337
84
                     .set(metadata::Identifier::CODESPACE_KEY, d->authority())
5338
84
                     .set(metadata::Identifier::CODE_KEY, code);
5339
84
    if (csType == CS_TYPE_ELLIPSOIDAL) {
5340
15
        if (axisList.size() == 2) {
5341
12
            return cacheAndRet(
5342
12
                cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
5343
12
        }
5344
3
        if (axisList.size() == 3) {
5345
3
            return cacheAndRet(cs::EllipsoidalCS::create(
5346
3
                props, axisList[0], axisList[1], axisList[2]));
5347
3
        }
5348
0
        throw FactoryException("invalid number of axis for EllipsoidalCS");
5349
3
    }
5350
69
    if (csType == CS_TYPE_CARTESIAN) {
5351
59
        if (axisList.size() == 2) {
5352
46
            return cacheAndRet(
5353
46
                cs::CartesianCS::create(props, axisList[0], axisList[1]));
5354
46
        }
5355
13
        if (axisList.size() == 3) {
5356
13
            return cacheAndRet(cs::CartesianCS::create(
5357
13
                props, axisList[0], axisList[1], axisList[2]));
5358
13
        }
5359
0
        throw FactoryException("invalid number of axis for CartesianCS");
5360
13
    }
5361
10
    if (csType == CS_TYPE_SPHERICAL) {
5362
1
        if (axisList.size() == 2) {
5363
1
            return cacheAndRet(
5364
1
                cs::SphericalCS::create(props, axisList[0], axisList[1]));
5365
1
        }
5366
0
        if (axisList.size() == 3) {
5367
0
            return cacheAndRet(cs::SphericalCS::create(
5368
0
                props, axisList[0], axisList[1], axisList[2]));
5369
0
        }
5370
0
        throw FactoryException("invalid number of axis for SphericalCS");
5371
0
    }
5372
9
    if (csType == CS_TYPE_VERTICAL) {
5373
9
        if (axisList.size() == 1) {
5374
9
            return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
5375
9
        }
5376
0
        throw FactoryException("invalid number of axis for VerticalCS");
5377
9
    }
5378
0
    if (csType == CS_TYPE_ORDINAL) {
5379
0
        return cacheAndRet(cs::OrdinalCS::create(props, axisList));
5380
0
    }
5381
0
    throw FactoryException("unhandled coordinate system type: " + csType);
5382
0
}
5383
5384
// ---------------------------------------------------------------------------
5385
5386
/** \brief Returns a crs::GeodeticCRS from the specified code.
5387
 *
5388
 * @param code Object code allocated by authority.
5389
 * @return object.
5390
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5391
 * @throw FactoryException in case of other errors.
5392
 */
5393
5394
crs::GeodeticCRSNNPtr
5395
7.02k
AuthorityFactory::createGeodeticCRS(const std::string &code) const {
5396
7.02k
    return createGeodeticCRS(code, false);
5397
7.02k
}
5398
5399
// ---------------------------------------------------------------------------
5400
5401
/** \brief Returns a crs::GeographicCRS from the specified code.
5402
 *
5403
 * @param code Object code allocated by authority.
5404
 * @return object.
5405
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5406
 * @throw FactoryException in case of other errors.
5407
 */
5408
5409
crs::GeographicCRSNNPtr
5410
216
AuthorityFactory::createGeographicCRS(const std::string &code) const {
5411
216
    auto crs(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
5412
216
        createGeodeticCRS(code, true)));
5413
216
    if (!crs) {
5414
0
        throw NoSuchAuthorityCodeException("geographicCRS not found",
5415
0
                                           d->authority(), code);
5416
0
    }
5417
216
    return NN_NO_CHECK(crs);
5418
216
}
5419
5420
// ---------------------------------------------------------------------------
5421
5422
static crs::GeodeticCRSNNPtr
5423
cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
5424
0
               const util::PropertyMap &props) {
5425
0
    auto cs = geodCRS->coordinateSystem();
5426
0
    auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5427
0
    if (ellipsoidalCS) {
5428
0
        return crs::GeographicCRS::create(props, geodCRS->datum(),
5429
0
                                          geodCRS->datumEnsemble(),
5430
0
                                          NN_NO_CHECK(ellipsoidalCS));
5431
0
    }
5432
0
    auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5433
0
    if (geocentricCS) {
5434
0
        return crs::GeodeticCRS::create(props, geodCRS->datum(),
5435
0
                                        geodCRS->datumEnsemble(),
5436
0
                                        NN_NO_CHECK(geocentricCS));
5437
0
    }
5438
0
    return geodCRS;
5439
0
}
5440
5441
// ---------------------------------------------------------------------------
5442
5443
crs::GeodeticCRSNNPtr
5444
AuthorityFactory::createGeodeticCRS(const std::string &code,
5445
7.24k
                                    bool geographicOnly) const {
5446
7.24k
    const auto cacheKey(d->authority() + code);
5447
7.24k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5448
7.24k
    if (crs) {
5449
4.06k
        auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
5450
4.06k
        if (geogCRS) {
5451
4.06k
            return NN_NO_CHECK(geogCRS);
5452
4.06k
        }
5453
0
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5454
0
                                           d->authority(), code);
5455
4.06k
    }
5456
3.18k
    std::string sql("SELECT name, type, coordinate_system_auth_name, "
5457
3.18k
                    "coordinate_system_code, datum_auth_name, datum_code, "
5458
3.18k
                    "text_definition, deprecated, description FROM "
5459
3.18k
                    "geodetic_crs WHERE auth_name = ? AND code = ?");
5460
3.18k
    if (geographicOnly) {
5461
216
        sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
5462
216
               ")";
5463
216
    }
5464
3.18k
    auto res = d->runWithCodeParam(sql, code);
5465
3.18k
    if (res.empty()) {
5466
216
        throw NoSuchAuthorityCodeException("geodeticCRS not found",
5467
216
                                           d->authority(), code);
5468
216
    }
5469
2.96k
    try {
5470
2.96k
        const auto &row = res.front();
5471
2.96k
        const auto &name = row[0];
5472
2.96k
        const auto &type = row[1];
5473
2.96k
        const auto &cs_auth_name = row[2];
5474
2.96k
        const auto &cs_code = row[3];
5475
2.96k
        const auto &datum_auth_name = row[4];
5476
2.96k
        const auto &datum_code = row[5];
5477
2.96k
        const auto &text_definition = row[6];
5478
2.96k
        const bool deprecated = row[7] == "1";
5479
2.96k
        const auto &remarks = row[8];
5480
5481
2.96k
        auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
5482
2.96k
                                                     deprecated, remarks);
5483
5484
2.96k
        if (!text_definition.empty()) {
5485
0
            DatabaseContext::Private::RecursionDetector detector(d->context());
5486
0
            auto obj = createFromUserInput(
5487
0
                pj_add_type_crs_if_needed(text_definition), d->context());
5488
0
            auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
5489
0
            if (geodCRS) {
5490
0
                auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
5491
0
                d->context()->d->cache(cacheKey, crsRet);
5492
0
                return crsRet;
5493
0
            }
5494
5495
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5496
0
            if (boundCRS) {
5497
0
                geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5498
0
                    boundCRS->baseCRS());
5499
0
                if (geodCRS) {
5500
0
                    auto newBoundCRS = crs::BoundCRS::create(
5501
0
                        cloneWithProps(NN_NO_CHECK(geodCRS), props),
5502
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5503
0
                    return NN_NO_CHECK(
5504
0
                        util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
5505
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5506
0
                }
5507
0
            }
5508
5509
0
            throw FactoryException(
5510
0
                "text_definition does not define a GeodeticCRS");
5511
0
        }
5512
5513
2.96k
        auto cs =
5514
2.96k
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5515
2.96k
        datum::GeodeticReferenceFramePtr datum;
5516
2.96k
        datum::DatumEnsemblePtr datumEnsemble;
5517
2.96k
        constexpr bool turnEnsembleAsDatum = false;
5518
2.96k
        d->createFactory(datum_auth_name)
5519
2.96k
            ->createGeodeticDatumOrEnsemble(datum_code, datum, datumEnsemble,
5520
2.96k
                                            turnEnsembleAsDatum);
5521
5522
2.96k
        auto ellipsoidalCS =
5523
2.96k
            util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
5524
2.96k
        if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) {
5525
2.36k
            auto crsRet = crs::GeographicCRS::create(
5526
2.36k
                props, datum, datumEnsemble, NN_NO_CHECK(ellipsoidalCS));
5527
2.36k
            d->context()->d->cache(cacheKey, crsRet);
5528
2.36k
            return crsRet;
5529
2.36k
        }
5530
5531
600
        auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5532
600
        if (type == GEOCENTRIC && geocentricCS) {
5533
592
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5534
592
                                                   NN_NO_CHECK(geocentricCS));
5535
592
            d->context()->d->cache(cacheKey, crsRet);
5536
592
            return crsRet;
5537
592
        }
5538
5539
8
        auto sphericalCS = util::nn_dynamic_pointer_cast<cs::SphericalCS>(cs);
5540
8
        if (type == OTHER && sphericalCS) {
5541
8
            auto crsRet = crs::GeodeticCRS::create(props, datum, datumEnsemble,
5542
8
                                                   NN_NO_CHECK(sphericalCS));
5543
8
            d->context()->d->cache(cacheKey, crsRet);
5544
8
            return crsRet;
5545
8
        }
5546
5547
0
        throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
5548
0
                               type + ", " + cs->getWKT2Type(true));
5549
8
    } catch (const std::exception &ex) {
5550
0
        throw buildFactoryException("geodeticCRS", d->authority(), code, ex);
5551
0
    }
5552
2.96k
}
5553
5554
// ---------------------------------------------------------------------------
5555
5556
/** \brief Returns a crs::VerticalCRS from the specified code.
5557
 *
5558
 * @param code Object code allocated by authority.
5559
 * @return object.
5560
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5561
 * @throw FactoryException in case of other errors.
5562
 */
5563
5564
crs::VerticalCRSNNPtr
5565
1.47k
AuthorityFactory::createVerticalCRS(const std::string &code) const {
5566
1.47k
    const auto cacheKey(d->authority() + code);
5567
1.47k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5568
1.47k
    if (crs) {
5569
927
        auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
5570
927
        if (projCRS) {
5571
927
            return NN_NO_CHECK(projCRS);
5572
927
        }
5573
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5574
0
                                           d->authority(), code);
5575
927
    }
5576
543
    auto res = d->runWithCodeParam(
5577
543
        "SELECT name, coordinate_system_auth_name, "
5578
543
        "coordinate_system_code, datum_auth_name, datum_code, "
5579
543
        "deprecated FROM "
5580
543
        "vertical_crs WHERE auth_name = ? AND code = ?",
5581
543
        code);
5582
543
    if (res.empty()) {
5583
0
        throw NoSuchAuthorityCodeException("verticalCRS not found",
5584
0
                                           d->authority(), code);
5585
0
    }
5586
543
    try {
5587
543
        const auto &row = res.front();
5588
543
        const auto &name = row[0];
5589
543
        const auto &cs_auth_name = row[1];
5590
543
        const auto &cs_code = row[2];
5591
543
        const auto &datum_auth_name = row[3];
5592
543
        const auto &datum_code = row[4];
5593
543
        const bool deprecated = row[5] == "1";
5594
543
        auto cs =
5595
543
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5596
543
        datum::VerticalReferenceFramePtr datum;
5597
543
        datum::DatumEnsemblePtr datumEnsemble;
5598
543
        constexpr bool turnEnsembleAsDatum = false;
5599
543
        d->createFactory(datum_auth_name)
5600
543
            ->createVerticalDatumOrEnsemble(datum_code, datum, datumEnsemble,
5601
543
                                            turnEnsembleAsDatum);
5602
543
        auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
5603
543
                                                     deprecated);
5604
5605
543
        auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
5606
543
        if (verticalCS) {
5607
543
            auto crsRet = crs::VerticalCRS::create(props, datum, datumEnsemble,
5608
543
                                                   NN_NO_CHECK(verticalCS));
5609
543
            d->context()->d->cache(cacheKey, crsRet);
5610
543
            return crsRet;
5611
543
        }
5612
0
        throw FactoryException("unsupported CS type for verticalCRS: " +
5613
0
                               cs->getWKT2Type(true));
5614
543
    } catch (const std::exception &ex) {
5615
0
        throw buildFactoryException("verticalCRS", d->authority(), code, ex);
5616
0
    }
5617
543
}
5618
5619
// ---------------------------------------------------------------------------
5620
5621
/** \brief Returns a crs::EngineeringCRS from the specified code.
5622
 *
5623
 * @param code Object code allocated by authority.
5624
 * @return object.
5625
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5626
 * @throw FactoryException in case of other errors.
5627
 * @since 9.6
5628
 */
5629
5630
crs::EngineeringCRSNNPtr
5631
16
AuthorityFactory::createEngineeringCRS(const std::string &code) const {
5632
16
    const auto cacheKey(d->authority() + code);
5633
16
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5634
16
    if (crs) {
5635
8
        auto engCRS = std::dynamic_pointer_cast<crs::EngineeringCRS>(crs);
5636
8
        if (engCRS) {
5637
8
            return NN_NO_CHECK(engCRS);
5638
8
        }
5639
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5640
0
                                           d->authority(), code);
5641
8
    }
5642
8
    auto res = d->runWithCodeParam(
5643
8
        "SELECT name, coordinate_system_auth_name, "
5644
8
        "coordinate_system_code, datum_auth_name, datum_code, "
5645
8
        "deprecated FROM "
5646
8
        "engineering_crs WHERE auth_name = ? AND code = ?",
5647
8
        code);
5648
8
    if (res.empty()) {
5649
0
        throw NoSuchAuthorityCodeException("engineeringCRS not found",
5650
0
                                           d->authority(), code);
5651
0
    }
5652
8
    try {
5653
8
        const auto &row = res.front();
5654
8
        const auto &name = row[0];
5655
8
        const auto &cs_auth_name = row[1];
5656
8
        const auto &cs_code = row[2];
5657
8
        const auto &datum_auth_name = row[3];
5658
8
        const auto &datum_code = row[4];
5659
8
        const bool deprecated = row[5] == "1";
5660
8
        auto cs =
5661
8
            d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5662
8
        auto datum = d->createFactory(datum_auth_name)
5663
8
                         ->createEngineeringDatum(datum_code);
5664
8
        auto props = d->createPropertiesSearchUsages("engineering_crs", code,
5665
8
                                                     name, deprecated);
5666
8
        auto crsRet = crs::EngineeringCRS::create(props, datum, cs);
5667
8
        d->context()->d->cache(cacheKey, crsRet);
5668
8
        return crsRet;
5669
8
    } catch (const std::exception &ex) {
5670
0
        throw buildFactoryException("engineeringCRS", d->authority(), code, ex);
5671
0
    }
5672
8
}
5673
5674
// ---------------------------------------------------------------------------
5675
5676
/** \brief Returns a operation::Conversion from the specified code.
5677
 *
5678
 * @param code Object code allocated by authority.
5679
 * @return object.
5680
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5681
 * @throw FactoryException in case of other errors.
5682
 */
5683
5684
operation::ConversionNNPtr
5685
1.88k
AuthorityFactory::createConversion(const std::string &code) const {
5686
5687
1.88k
    static const char *sql =
5688
1.88k
        "SELECT name, description, "
5689
1.88k
        "method_auth_name, method_code, method_name, "
5690
5691
1.88k
        "param1_auth_name, param1_code, param1_name, param1_value, "
5692
1.88k
        "param1_uom_auth_name, param1_uom_code, "
5693
5694
1.88k
        "param2_auth_name, param2_code, param2_name, param2_value, "
5695
1.88k
        "param2_uom_auth_name, param2_uom_code, "
5696
5697
1.88k
        "param3_auth_name, param3_code, param3_name, param3_value, "
5698
1.88k
        "param3_uom_auth_name, param3_uom_code, "
5699
5700
1.88k
        "param4_auth_name, param4_code, param4_name, param4_value, "
5701
1.88k
        "param4_uom_auth_name, param4_uom_code, "
5702
5703
1.88k
        "param5_auth_name, param5_code, param5_name, param5_value, "
5704
1.88k
        "param5_uom_auth_name, param5_uom_code, "
5705
5706
1.88k
        "param6_auth_name, param6_code, param6_name, param6_value, "
5707
1.88k
        "param6_uom_auth_name, param6_uom_code, "
5708
5709
1.88k
        "param7_auth_name, param7_code, param7_name, param7_value, "
5710
1.88k
        "param7_uom_auth_name, param7_uom_code, "
5711
5712
1.88k
        "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
5713
5714
1.88k
    auto res = d->runWithCodeParam(sql, code);
5715
1.88k
    if (res.empty()) {
5716
0
        try {
5717
            // Conversions using methods Change of Vertical Unit or
5718
            // Height Depth Reversal are stored in other_transformation
5719
0
            auto op = createCoordinateOperation(
5720
0
                code, false /* allowConcatenated */,
5721
0
                false /* usePROJAlternativeGridNames */,
5722
0
                "other_transformation");
5723
0
            auto conv =
5724
0
                util::nn_dynamic_pointer_cast<operation::Conversion>(op);
5725
0
            if (conv) {
5726
0
                return NN_NO_CHECK(conv);
5727
0
            }
5728
0
        } catch (const std::exception &) {
5729
0
        }
5730
0
        throw NoSuchAuthorityCodeException("conversion not found",
5731
0
                                           d->authority(), code);
5732
0
    }
5733
1.88k
    try {
5734
1.88k
        const auto &row = res.front();
5735
1.88k
        size_t idx = 0;
5736
1.88k
        const auto &name = row[idx++];
5737
1.88k
        const auto &description = row[idx++];
5738
1.88k
        const auto &method_auth_name = row[idx++];
5739
1.88k
        const auto &method_code = row[idx++];
5740
1.88k
        const auto &method_name = row[idx++];
5741
1.88k
        const size_t base_param_idx = idx;
5742
1.88k
        std::vector<operation::OperationParameterNNPtr> parameters;
5743
1.88k
        std::vector<operation::ParameterValueNNPtr> values;
5744
11.4k
        for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
5745
11.4k
            const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
5746
11.4k
            if (param_auth_name.empty()) {
5747
1.87k
                break;
5748
1.87k
            }
5749
9.59k
            const auto &param_code = row[base_param_idx + i * 6 + 1];
5750
9.59k
            const auto &param_name = row[base_param_idx + i * 6 + 2];
5751
9.59k
            const auto &param_value = row[base_param_idx + i * 6 + 3];
5752
9.59k
            const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
5753
9.59k
            const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
5754
9.59k
            parameters.emplace_back(operation::OperationParameter::create(
5755
9.59k
                util::PropertyMap()
5756
9.59k
                    .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
5757
9.59k
                    .set(metadata::Identifier::CODE_KEY, param_code)
5758
9.59k
                    .set(common::IdentifiedObject::NAME_KEY, param_name)));
5759
9.59k
            std::string normalized_uom_code(param_uom_code);
5760
9.59k
            const double normalized_value = normalizeMeasure(
5761
9.59k
                param_uom_code, param_value, normalized_uom_code);
5762
9.59k
            auto uom = d->createUnitOfMeasure(param_uom_auth_name,
5763
9.59k
                                              normalized_uom_code);
5764
9.59k
            values.emplace_back(operation::ParameterValue::create(
5765
9.59k
                common::Measure(normalized_value, uom)));
5766
9.59k
        }
5767
1.88k
        const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
5768
5769
1.88k
        auto propConversion = d->createPropertiesSearchUsages(
5770
1.88k
            "conversion", code, name, deprecated);
5771
1.88k
        if (!description.empty())
5772
935
            propConversion.set(common::IdentifiedObject::REMARKS_KEY,
5773
935
                               description);
5774
5775
1.88k
        auto propMethod = util::PropertyMap().set(
5776
1.88k
            common::IdentifiedObject::NAME_KEY, method_name);
5777
1.88k
        if (!method_auth_name.empty()) {
5778
1.88k
            propMethod
5779
1.88k
                .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
5780
1.88k
                .set(metadata::Identifier::CODE_KEY, method_code);
5781
1.88k
        }
5782
5783
1.88k
        return operation::Conversion::create(propConversion, propMethod,
5784
1.88k
                                             parameters, values);
5785
1.88k
    } catch (const std::exception &ex) {
5786
0
        throw buildFactoryException("conversion", d->authority(), code, ex);
5787
0
    }
5788
1.88k
}
5789
5790
// ---------------------------------------------------------------------------
5791
5792
/** \brief Returns a crs::ProjectedCRS from the specified code.
5793
 *
5794
 * @param code Object code allocated by authority.
5795
 * @return object.
5796
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5797
 * @throw FactoryException in case of other errors.
5798
 */
5799
5800
crs::ProjectedCRSNNPtr
5801
2.90k
AuthorityFactory::createProjectedCRS(const std::string &code) const {
5802
2.90k
    const auto cacheKey(d->authority() + code);
5803
2.90k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5804
2.90k
    if (crs) {
5805
1.19k
        auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
5806
1.19k
        if (projCRS) {
5807
1.19k
            return NN_NO_CHECK(projCRS);
5808
1.19k
        }
5809
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5810
0
                                           d->authority(), code);
5811
1.19k
    }
5812
1.70k
    return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
5813
2.90k
}
5814
5815
// ---------------------------------------------------------------------------
5816
//! @cond Doxygen_Suppress
5817
5818
/** Returns the result of the SQL query needed by createProjectedCRSEnd
5819
 *
5820
 * The split in two functions is for createFromCoordinateReferenceSystemCodes()
5821
 * convenience, to avoid throwing exceptions.
5822
 */
5823
SQLResultSet
5824
1.70k
AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
5825
1.70k
    return runWithCodeParam(
5826
1.70k
        "SELECT name, coordinate_system_auth_name, "
5827
1.70k
        "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
5828
1.70k
        "conversion_auth_name, conversion_code, "
5829
1.70k
        "text_definition, "
5830
1.70k
        "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
5831
1.70k
        code);
5832
1.70k
}
5833
5834
// ---------------------------------------------------------------------------
5835
5836
/** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
5837
crs::ProjectedCRSNNPtr
5838
AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
5839
1.70k
                                                 const SQLResultSet &res) {
5840
1.70k
    const auto cacheKey(authority() + code);
5841
1.70k
    if (res.empty()) {
5842
0
        throw NoSuchAuthorityCodeException("projectedCRS not found",
5843
0
                                           authority(), code);
5844
0
    }
5845
1.70k
    try {
5846
1.70k
        const auto &row = res.front();
5847
1.70k
        const auto &name = row[0];
5848
1.70k
        const auto &cs_auth_name = row[1];
5849
1.70k
        const auto &cs_code = row[2];
5850
1.70k
        const auto &geodetic_crs_auth_name = row[3];
5851
1.70k
        const auto &geodetic_crs_code = row[4];
5852
1.70k
        const auto &conversion_auth_name = row[5];
5853
1.70k
        const auto &conversion_code = row[6];
5854
1.70k
        const auto &text_definition = row[7];
5855
1.70k
        const bool deprecated = row[8] == "1";
5856
5857
1.70k
        auto props = createPropertiesSearchUsages("projected_crs", code, name,
5858
1.70k
                                                  deprecated);
5859
5860
1.70k
        if (!text_definition.empty()) {
5861
85
            DatabaseContext::Private::RecursionDetector detector(context());
5862
85
            auto obj = createFromUserInput(
5863
85
                pj_add_type_crs_if_needed(text_definition), context());
5864
85
            auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
5865
85
            if (projCRS) {
5866
85
                auto conv = projCRS->derivingConversion();
5867
85
                auto newConv =
5868
85
                    (conv->nameStr() == "unnamed")
5869
85
                        ? operation::Conversion::create(
5870
77
                              util::PropertyMap().set(
5871
77
                                  common::IdentifiedObject::NAME_KEY, name),
5872
77
                              conv->method(), conv->parameterValues())
5873
85
                        : std::move(conv);
5874
85
                auto crsRet = crs::ProjectedCRS::create(
5875
85
                    props, projCRS->baseCRS(), newConv,
5876
85
                    projCRS->coordinateSystem());
5877
85
                context()->d->cache(cacheKey, crsRet);
5878
85
                return crsRet;
5879
85
            }
5880
5881
0
            auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
5882
0
            if (boundCRS) {
5883
0
                projCRS = dynamic_cast<const crs::ProjectedCRS *>(
5884
0
                    boundCRS->baseCRS().get());
5885
0
                if (projCRS) {
5886
0
                    auto newBoundCRS = crs::BoundCRS::create(
5887
0
                        crs::ProjectedCRS::create(props, projCRS->baseCRS(),
5888
0
                                                  projCRS->derivingConversion(),
5889
0
                                                  projCRS->coordinateSystem()),
5890
0
                        boundCRS->hubCRS(), boundCRS->transformation());
5891
0
                    return NN_NO_CHECK(
5892
0
                        util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
5893
0
                            newBoundCRS->baseCRSWithCanonicalBoundCRS()));
5894
0
                }
5895
0
            }
5896
5897
0
            throw FactoryException(
5898
0
                "text_definition does not define a ProjectedCRS");
5899
0
        }
5900
5901
1.62k
        auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
5902
5903
1.62k
        auto baseCRS = createFactory(geodetic_crs_auth_name)
5904
1.62k
                           ->createGeodeticCRS(geodetic_crs_code);
5905
5906
1.62k
        auto conv = createFactory(conversion_auth_name)
5907
1.62k
                        ->createConversion(conversion_code);
5908
1.62k
        if (conv->nameStr() == "unnamed") {
5909
151
            conv = conv->shallowClone();
5910
151
            conv->setProperties(util::PropertyMap().set(
5911
151
                common::IdentifiedObject::NAME_KEY, name));
5912
151
        }
5913
5914
1.62k
        auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
5915
1.62k
        if (cartesianCS) {
5916
1.62k
            auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
5917
1.62k
                                                    NN_NO_CHECK(cartesianCS));
5918
1.62k
            context()->d->cache(cacheKey, crsRet);
5919
1.62k
            return crsRet;
5920
1.62k
        }
5921
0
        throw FactoryException("unsupported CS type for projectedCRS: " +
5922
0
                               cs->getWKT2Type(true));
5923
1.62k
    } catch (const std::exception &ex) {
5924
0
        throw buildFactoryException("projectedCRS", authority(), code, ex);
5925
0
    }
5926
1.70k
}
5927
//! @endcond
5928
5929
// ---------------------------------------------------------------------------
5930
5931
/** \brief Returns a crs::CompoundCRS from the specified code.
5932
 *
5933
 * @param code Object code allocated by authority.
5934
 * @return object.
5935
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5936
 * @throw FactoryException in case of other errors.
5937
 */
5938
5939
crs::CompoundCRSNNPtr
5940
406
AuthorityFactory::createCompoundCRS(const std::string &code) const {
5941
406
    auto res =
5942
406
        d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
5943
406
                            "vertical_crs_auth_name, vertical_crs_code, "
5944
406
                            "deprecated FROM "
5945
406
                            "compound_crs WHERE auth_name = ? AND code = ?",
5946
406
                            code);
5947
406
    if (res.empty()) {
5948
0
        throw NoSuchAuthorityCodeException("compoundCRS not found",
5949
0
                                           d->authority(), code);
5950
0
    }
5951
406
    try {
5952
406
        const auto &row = res.front();
5953
406
        const auto &name = row[0];
5954
406
        const auto &horiz_crs_auth_name = row[1];
5955
406
        const auto &horiz_crs_code = row[2];
5956
406
        const auto &vertical_crs_auth_name = row[3];
5957
406
        const auto &vertical_crs_code = row[4];
5958
406
        const bool deprecated = row[5] == "1";
5959
5960
406
        auto horizCRS =
5961
406
            d->createFactory(horiz_crs_auth_name)
5962
406
                ->createCoordinateReferenceSystem(horiz_crs_code, false);
5963
406
        auto vertCRS = d->createFactory(vertical_crs_auth_name)
5964
406
                           ->createVerticalCRS(vertical_crs_code);
5965
5966
406
        auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
5967
406
                                                     deprecated);
5968
406
        return crs::CompoundCRS::create(
5969
406
            props, std::vector<crs::CRSNNPtr>{std::move(horizCRS),
5970
406
                                              std::move(vertCRS)});
5971
406
    } catch (const std::exception &ex) {
5972
0
        throw buildFactoryException("compoundCRS", d->authority(), code, ex);
5973
0
    }
5974
406
}
5975
5976
// ---------------------------------------------------------------------------
5977
5978
/** \brief Returns a crs::CRS from the specified code.
5979
 *
5980
 * @param code Object code allocated by authority.
5981
 * @return object.
5982
 * @throw NoSuchAuthorityCodeException if there is no matching object.
5983
 * @throw FactoryException in case of other errors.
5984
 */
5985
5986
crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
5987
6.06k
    const std::string &code) const {
5988
6.06k
    return createCoordinateReferenceSystem(code, true);
5989
6.06k
}
5990
5991
//! @cond Doxygen_Suppress
5992
5993
crs::CRSNNPtr
5994
AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
5995
6.77k
                                                  bool allowCompound) const {
5996
6.77k
    const auto cacheKey(d->authority() + code);
5997
6.77k
    auto crs = d->context()->d->getCRSFromCache(cacheKey);
5998
6.77k
    if (crs) {
5999
3.56k
        return NN_NO_CHECK(crs);
6000
3.56k
    }
6001
6002
3.20k
    if (d->authority() == metadata::Identifier::OGC) {
6003
153
        if (code == "AnsiDate") {
6004
            // Derived from http://www.opengis.net/def/crs/OGC/0/AnsiDate
6005
40
            return crs::TemporalCRS::create(
6006
40
                util::PropertyMap()
6007
                    // above URL indicates Julian Date" as name... likely wrong
6008
40
                    .set(common::IdentifiedObject::NAME_KEY, "Ansi Date")
6009
40
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6010
40
                    .set(metadata::Identifier::CODE_KEY, code),
6011
40
                datum::TemporalDatum::create(
6012
40
                    util::PropertyMap().set(
6013
40
                        common::IdentifiedObject::NAME_KEY,
6014
40
                        "Epoch time for the ANSI date (1-Jan-1601, 00h00 UTC) "
6015
40
                        "as day 1."),
6016
40
                    common::DateTime::create("1600-12-31T00:00:00Z"),
6017
40
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6018
40
                cs::TemporalCountCS::create(
6019
40
                    util::PropertyMap(),
6020
40
                    cs::CoordinateSystemAxis::create(
6021
40
                        util::PropertyMap().set(
6022
40
                            common::IdentifiedObject::NAME_KEY, "Time"),
6023
40
                        "T", cs::AxisDirection::FUTURE,
6024
40
                        common::UnitOfMeasure("day", 0,
6025
40
                                              UnitOfMeasure::Type::TIME))));
6026
40
        }
6027
113
        if (code == "JulianDate") {
6028
            // Derived from http://www.opengis.net/def/crs/OGC/0/JulianDate
6029
0
            return crs::TemporalCRS::create(
6030
0
                util::PropertyMap()
6031
0
                    .set(common::IdentifiedObject::NAME_KEY, "Julian Date")
6032
0
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6033
0
                    .set(metadata::Identifier::CODE_KEY, code),
6034
0
                datum::TemporalDatum::create(
6035
0
                    util::PropertyMap().set(
6036
0
                        common::IdentifiedObject::NAME_KEY,
6037
0
                        "The beginning of the Julian period."),
6038
0
                    common::DateTime::create("-4714-11-24T12:00:00Z"),
6039
0
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6040
0
                cs::TemporalCountCS::create(
6041
0
                    util::PropertyMap(),
6042
0
                    cs::CoordinateSystemAxis::create(
6043
0
                        util::PropertyMap().set(
6044
0
                            common::IdentifiedObject::NAME_KEY, "Time"),
6045
0
                        "T", cs::AxisDirection::FUTURE,
6046
0
                        common::UnitOfMeasure("day", 0,
6047
0
                                              UnitOfMeasure::Type::TIME))));
6048
0
        }
6049
113
        if (code == "UnixTime") {
6050
            // Derived from http://www.opengis.net/def/crs/OGC/0/UnixTime
6051
80
            return crs::TemporalCRS::create(
6052
80
                util::PropertyMap()
6053
80
                    .set(common::IdentifiedObject::NAME_KEY, "Unix Time")
6054
80
                    .set(metadata::Identifier::CODESPACE_KEY, d->authority())
6055
80
                    .set(metadata::Identifier::CODE_KEY, code),
6056
80
                datum::TemporalDatum::create(
6057
80
                    util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6058
80
                                            "Unix epoch"),
6059
80
                    common::DateTime::create("1970-01-01T00:00:00Z"),
6060
80
                    datum::TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN),
6061
80
                cs::TemporalCountCS::create(
6062
80
                    util::PropertyMap(),
6063
80
                    cs::CoordinateSystemAxis::create(
6064
80
                        util::PropertyMap().set(
6065
80
                            common::IdentifiedObject::NAME_KEY, "Time"),
6066
80
                        "T", cs::AxisDirection::FUTURE,
6067
80
                        common::UnitOfMeasure::SECOND)));
6068
80
        }
6069
33
        if (code == "84") {
6070
0
            return createCoordinateReferenceSystem("CRS84", false);
6071
0
        }
6072
33
    }
6073
6074
3.08k
    auto res = d->runWithCodeParam(
6075
3.08k
        "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
6076
3.08k
    if (res.empty()) {
6077
1.98k
        throw NoSuchAuthorityCodeException("crs not found", d->authority(),
6078
1.98k
                                           code);
6079
1.98k
    }
6080
1.10k
    const auto &type = res.front()[0];
6081
1.10k
    if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC ||
6082
1.10k
        type == OTHER) {
6083
719
        return createGeodeticCRS(code);
6084
719
    }
6085
384
    if (type == VERTICAL) {
6086
74
        return createVerticalCRS(code);
6087
74
    }
6088
310
    if (type == PROJECTED) {
6089
169
        return createProjectedCRS(code);
6090
169
    }
6091
141
    if (type == ENGINEERING) {
6092
1
        return createEngineeringCRS(code);
6093
1
    }
6094
140
    if (allowCompound && type == COMPOUND) {
6095
140
        return createCompoundCRS(code);
6096
140
    }
6097
0
    throw FactoryException("unhandled CRS type: " + type);
6098
140
}
6099
6100
//! @endcond
6101
6102
// ---------------------------------------------------------------------------
6103
6104
/** \brief Returns a coordinates::CoordinateMetadata from the specified code.
6105
 *
6106
 * @param code Object code allocated by authority.
6107
 * @return object.
6108
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6109
 * @throw FactoryException in case of other errors.
6110
 * @since 9.4
6111
 */
6112
6113
coordinates::CoordinateMetadataNNPtr
6114
0
AuthorityFactory::createCoordinateMetadata(const std::string &code) const {
6115
0
    auto res = d->runWithCodeParam(
6116
0
        "SELECT crs_auth_name, crs_code, crs_text_definition, coordinate_epoch "
6117
0
        "FROM coordinate_metadata WHERE auth_name = ? AND code = ?",
6118
0
        code);
6119
0
    if (res.empty()) {
6120
0
        throw NoSuchAuthorityCodeException("coordinate_metadata not found",
6121
0
                                           d->authority(), code);
6122
0
    }
6123
0
    try {
6124
0
        const auto &row = res.front();
6125
0
        const auto &crs_auth_name = row[0];
6126
0
        const auto &crs_code = row[1];
6127
0
        const auto &crs_text_definition = row[2];
6128
0
        const auto &coordinate_epoch = row[3];
6129
6130
0
        auto l_context = d->context();
6131
0
        DatabaseContext::Private::RecursionDetector detector(l_context);
6132
0
        auto crs =
6133
0
            !crs_auth_name.empty()
6134
0
                ? d->createFactory(crs_auth_name)
6135
0
                      ->createCoordinateReferenceSystem(crs_code)
6136
0
                      .as_nullable()
6137
0
                : util::nn_dynamic_pointer_cast<crs::CRS>(
6138
0
                      createFromUserInput(crs_text_definition, l_context));
6139
0
        if (!crs) {
6140
0
            throw FactoryException(
6141
0
                std::string("cannot build CoordinateMetadata ") +
6142
0
                d->authority() + ":" + code + ": cannot build CRS");
6143
0
        }
6144
0
        if (coordinate_epoch.empty()) {
6145
0
            return coordinates::CoordinateMetadata::create(NN_NO_CHECK(crs));
6146
0
        } else {
6147
0
            return coordinates::CoordinateMetadata::create(
6148
0
                NN_NO_CHECK(crs), c_locale_stod(coordinate_epoch),
6149
0
                l_context.as_nullable());
6150
0
        }
6151
0
    } catch (const std::exception &ex) {
6152
0
        throw buildFactoryException("CoordinateMetadata", d->authority(), code,
6153
0
                                    ex);
6154
0
    }
6155
0
}
6156
6157
// ---------------------------------------------------------------------------
6158
6159
//! @cond Doxygen_Suppress
6160
6161
static util::PropertyMap createMapNameEPSGCode(const std::string &name,
6162
4.89k
                                               int code) {
6163
4.89k
    return util::PropertyMap()
6164
4.89k
        .set(common::IdentifiedObject::NAME_KEY, name)
6165
4.89k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
6166
4.89k
        .set(metadata::Identifier::CODE_KEY, code);
6167
4.89k
}
6168
6169
// ---------------------------------------------------------------------------
6170
6171
4.89k
static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
6172
4.89k
    const char *name = operation::OperationParameter::getNameForEPSGCode(code);
6173
4.89k
    assert(name);
6174
4.89k
    return operation::OperationParameter::create(
6175
4.89k
        createMapNameEPSGCode(name, code));
6176
4.89k
}
6177
6178
static operation::ParameterValueNNPtr createLength(const std::string &value,
6179
2.40k
                                                   const UnitOfMeasure &uom) {
6180
2.40k
    return operation::ParameterValue::create(
6181
2.40k
        common::Length(c_locale_stod(value), uom));
6182
2.40k
}
6183
6184
static operation::ParameterValueNNPtr createAngle(const std::string &value,
6185
1.73k
                                                  const UnitOfMeasure &uom) {
6186
1.73k
    return operation::ParameterValue::create(
6187
1.73k
        common::Angle(c_locale_stod(value), uom));
6188
1.73k
}
6189
6190
//! @endcond
6191
6192
// ---------------------------------------------------------------------------
6193
6194
/** \brief Returns a operation::CoordinateOperation from the specified code.
6195
 *
6196
 * @param code Object code allocated by authority.
6197
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6198
 * should be substituted to the official grid names.
6199
 * @return object.
6200
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6201
 * @throw FactoryException in case of other errors.
6202
 */
6203
6204
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6205
1.10k
    const std::string &code, bool usePROJAlternativeGridNames) const {
6206
1.10k
    return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
6207
1.10k
                                     std::string());
6208
1.10k
}
6209
6210
operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
6211
    const std::string &code, bool allowConcatenated,
6212
1.32k
    bool usePROJAlternativeGridNames, const std::string &typeIn) const {
6213
1.32k
    std::string type(typeIn);
6214
1.32k
    if (type.empty()) {
6215
1.32k
        auto res = d->runWithCodeParam(
6216
1.32k
            "SELECT type FROM coordinate_operation_with_conversion_view "
6217
1.32k
            "WHERE auth_name = ? AND code = ?",
6218
1.32k
            code);
6219
1.32k
        if (res.empty()) {
6220
0
            throw NoSuchAuthorityCodeException("coordinate operation not found",
6221
0
                                               d->authority(), code);
6222
0
        }
6223
1.32k
        type = res.front()[0];
6224
1.32k
    }
6225
6226
1.32k
    if (type == "conversion") {
6227
0
        return createConversion(code);
6228
0
    }
6229
6230
1.32k
    if (type == "helmert_transformation") {
6231
6232
665
        auto res = d->runWithCodeParam(
6233
665
            "SELECT name, description, "
6234
665
            "method_auth_name, method_code, method_name, "
6235
665
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6236
665
            "target_crs_code, "
6237
665
            "accuracy, tx, ty, tz, translation_uom_auth_name, "
6238
665
            "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
6239
665
            "rotation_uom_code, scale_difference, "
6240
665
            "scale_difference_uom_auth_name, scale_difference_uom_code, "
6241
665
            "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
6242
665
            "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
6243
665
            "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
6244
665
            "rate_scale_difference, rate_scale_difference_uom_auth_name, "
6245
665
            "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
6246
665
            "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
6247
665
            "operation_version, deprecated FROM "
6248
665
            "helmert_transformation WHERE auth_name = ? AND code = ?",
6249
665
            code);
6250
665
        if (res.empty()) {
6251
            // shouldn't happen if foreign keys are OK
6252
0
            throw NoSuchAuthorityCodeException(
6253
0
                "helmert_transformation not found", d->authority(), code);
6254
0
        }
6255
665
        try {
6256
665
            const auto &row = res.front();
6257
665
            size_t idx = 0;
6258
665
            const auto &name = row[idx++];
6259
665
            const auto &description = row[idx++];
6260
665
            const auto &method_auth_name = row[idx++];
6261
665
            const auto &method_code = row[idx++];
6262
665
            const auto &method_name = row[idx++];
6263
665
            const auto &source_crs_auth_name = row[idx++];
6264
665
            const auto &source_crs_code = row[idx++];
6265
665
            const auto &target_crs_auth_name = row[idx++];
6266
665
            const auto &target_crs_code = row[idx++];
6267
665
            const auto &accuracy = row[idx++];
6268
6269
665
            const auto &tx = row[idx++];
6270
665
            const auto &ty = row[idx++];
6271
665
            const auto &tz = row[idx++];
6272
665
            const auto &translation_uom_auth_name = row[idx++];
6273
665
            const auto &translation_uom_code = row[idx++];
6274
665
            const auto &rx = row[idx++];
6275
665
            const auto &ry = row[idx++];
6276
665
            const auto &rz = row[idx++];
6277
665
            const auto &rotation_uom_auth_name = row[idx++];
6278
665
            const auto &rotation_uom_code = row[idx++];
6279
665
            const auto &scale_difference = row[idx++];
6280
665
            const auto &scale_difference_uom_auth_name = row[idx++];
6281
665
            const auto &scale_difference_uom_code = row[idx++];
6282
6283
665
            const auto &rate_tx = row[idx++];
6284
665
            const auto &rate_ty = row[idx++];
6285
665
            const auto &rate_tz = row[idx++];
6286
665
            const auto &rate_translation_uom_auth_name = row[idx++];
6287
665
            const auto &rate_translation_uom_code = row[idx++];
6288
665
            const auto &rate_rx = row[idx++];
6289
665
            const auto &rate_ry = row[idx++];
6290
665
            const auto &rate_rz = row[idx++];
6291
665
            const auto &rate_rotation_uom_auth_name = row[idx++];
6292
665
            const auto &rate_rotation_uom_code = row[idx++];
6293
665
            const auto &rate_scale_difference = row[idx++];
6294
665
            const auto &rate_scale_difference_uom_auth_name = row[idx++];
6295
665
            const auto &rate_scale_difference_uom_code = row[idx++];
6296
6297
665
            const auto &epoch = row[idx++];
6298
665
            const auto &epoch_uom_auth_name = row[idx++];
6299
665
            const auto &epoch_uom_code = row[idx++];
6300
6301
665
            const auto &px = row[idx++];
6302
665
            const auto &py = row[idx++];
6303
665
            const auto &pz = row[idx++];
6304
665
            const auto &pivot_uom_auth_name = row[idx++];
6305
665
            const auto &pivot_uom_code = row[idx++];
6306
6307
665
            const auto &operation_version = row[idx++];
6308
665
            const auto &deprecated_str = row[idx++];
6309
665
            const bool deprecated = deprecated_str == "1";
6310
665
            assert(idx == row.size());
6311
6312
665
            auto uom_translation = d->createUnitOfMeasure(
6313
665
                translation_uom_auth_name, translation_uom_code);
6314
6315
665
            auto uom_epoch = epoch_uom_auth_name.empty()
6316
665
                                 ? common::UnitOfMeasure::NONE
6317
665
                                 : d->createUnitOfMeasure(epoch_uom_auth_name,
6318
168
                                                          epoch_uom_code);
6319
6320
665
            auto sourceCRS =
6321
665
                d->createFactory(source_crs_auth_name)
6322
665
                    ->createCoordinateReferenceSystem(source_crs_code);
6323
665
            auto targetCRS =
6324
665
                d->createFactory(target_crs_auth_name)
6325
665
                    ->createCoordinateReferenceSystem(target_crs_code);
6326
6327
665
            std::vector<operation::OperationParameterNNPtr> parameters;
6328
665
            std::vector<operation::ParameterValueNNPtr> values;
6329
6330
665
            parameters.emplace_back(createOpParamNameEPSGCode(
6331
665
                EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
6332
665
            values.emplace_back(createLength(tx, uom_translation));
6333
6334
665
            parameters.emplace_back(createOpParamNameEPSGCode(
6335
665
                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
6336
665
            values.emplace_back(createLength(ty, uom_translation));
6337
6338
665
            parameters.emplace_back(createOpParamNameEPSGCode(
6339
665
                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
6340
665
            values.emplace_back(createLength(tz, uom_translation));
6341
6342
665
            if (!rx.empty()) {
6343
                // Helmert 7-, 8-, 10- or 15- parameter cases
6344
453
                auto uom_rotation = d->createUnitOfMeasure(
6345
453
                    rotation_uom_auth_name, rotation_uom_code);
6346
6347
453
                parameters.emplace_back(createOpParamNameEPSGCode(
6348
453
                    EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
6349
453
                values.emplace_back(createAngle(rx, uom_rotation));
6350
6351
453
                parameters.emplace_back(createOpParamNameEPSGCode(
6352
453
                    EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
6353
453
                values.emplace_back(createAngle(ry, uom_rotation));
6354
6355
453
                parameters.emplace_back(createOpParamNameEPSGCode(
6356
453
                    EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
6357
453
                values.emplace_back(createAngle(rz, uom_rotation));
6358
6359
453
                auto uom_scale_difference =
6360
453
                    scale_difference_uom_auth_name.empty()
6361
453
                        ? common::UnitOfMeasure::NONE
6362
453
                        : d->createUnitOfMeasure(scale_difference_uom_auth_name,
6363
453
                                                 scale_difference_uom_code);
6364
6365
453
                parameters.emplace_back(createOpParamNameEPSGCode(
6366
453
                    EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
6367
453
                values.emplace_back(operation::ParameterValue::create(
6368
453
                    common::Scale(c_locale_stod(scale_difference),
6369
453
                                  uom_scale_difference)));
6370
453
            }
6371
6372
665
            if (!rate_tx.empty()) {
6373
                // Helmert 15-parameter
6374
6375
126
                auto uom_rate_translation = d->createUnitOfMeasure(
6376
126
                    rate_translation_uom_auth_name, rate_translation_uom_code);
6377
6378
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6379
126
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
6380
126
                values.emplace_back(
6381
126
                    createLength(rate_tx, uom_rate_translation));
6382
6383
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6384
126
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
6385
126
                values.emplace_back(
6386
126
                    createLength(rate_ty, uom_rate_translation));
6387
6388
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6389
126
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
6390
126
                values.emplace_back(
6391
126
                    createLength(rate_tz, uom_rate_translation));
6392
6393
126
                auto uom_rate_rotation = d->createUnitOfMeasure(
6394
126
                    rate_rotation_uom_auth_name, rate_rotation_uom_code);
6395
6396
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6397
126
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
6398
126
                values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
6399
6400
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6401
126
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
6402
126
                values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
6403
6404
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6405
126
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
6406
126
                values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
6407
6408
126
                auto uom_rate_scale_difference =
6409
126
                    d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
6410
126
                                           rate_scale_difference_uom_code);
6411
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6412
126
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
6413
126
                values.emplace_back(operation::ParameterValue::create(
6414
126
                    common::Scale(c_locale_stod(rate_scale_difference),
6415
126
                                  uom_rate_scale_difference)));
6416
6417
126
                parameters.emplace_back(createOpParamNameEPSGCode(
6418
126
                    EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
6419
126
                values.emplace_back(operation::ParameterValue::create(
6420
126
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6421
539
            } else if (uom_epoch != common::UnitOfMeasure::NONE) {
6422
                // Helmert 8-parameter
6423
42
                parameters.emplace_back(createOpParamNameEPSGCode(
6424
42
                    EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
6425
42
                values.emplace_back(operation::ParameterValue::create(
6426
42
                    common::Measure(c_locale_stod(epoch), uom_epoch)));
6427
497
            } else if (!px.empty()) {
6428
                // Molodensky-Badekas case
6429
12
                auto uom_pivot =
6430
12
                    d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
6431
6432
12
                parameters.emplace_back(createOpParamNameEPSGCode(
6433
12
                    EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
6434
12
                values.emplace_back(createLength(px, uom_pivot));
6435
6436
12
                parameters.emplace_back(createOpParamNameEPSGCode(
6437
12
                    EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
6438
12
                values.emplace_back(createLength(py, uom_pivot));
6439
6440
12
                parameters.emplace_back(createOpParamNameEPSGCode(
6441
12
                    EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
6442
12
                values.emplace_back(createLength(pz, uom_pivot));
6443
12
            }
6444
6445
665
            auto props = d->createPropertiesSearchUsages(
6446
665
                type, code, name, deprecated, description);
6447
665
            if (!operation_version.empty()) {
6448
560
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6449
560
                          operation_version);
6450
560
            }
6451
6452
665
            auto propsMethod =
6453
665
                util::PropertyMap()
6454
665
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6455
665
                    .set(metadata::Identifier::CODE_KEY, method_code)
6456
665
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6457
6458
665
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6459
665
            if (!accuracy.empty() && accuracy != "999.0") {
6460
571
                accuracies.emplace_back(
6461
571
                    metadata::PositionalAccuracy::create(accuracy));
6462
571
            }
6463
665
            return operation::Transformation::create(
6464
665
                props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
6465
665
                values, accuracies);
6466
6467
665
        } catch (const std::exception &ex) {
6468
0
            throw buildFactoryException("transformation", d->authority(), code,
6469
0
                                        ex);
6470
0
        }
6471
665
    }
6472
6473
664
    if (type == "grid_transformation") {
6474
402
        auto res = d->runWithCodeParam(
6475
402
            "SELECT name, description, "
6476
402
            "method_auth_name, method_code, method_name, "
6477
402
            "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6478
402
            "target_crs_code, "
6479
402
            "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
6480
402
            "grid_name, "
6481
402
            "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
6482
402
            "grid2_name, "
6483
402
            "interpolation_crs_auth_name, interpolation_crs_code, "
6484
402
            "operation_version, deprecated FROM "
6485
402
            "grid_transformation WHERE auth_name = ? AND code = ?",
6486
402
            code);
6487
402
        if (res.empty()) {
6488
            // shouldn't happen if foreign keys are OK
6489
0
            throw NoSuchAuthorityCodeException("grid_transformation not found",
6490
0
                                               d->authority(), code);
6491
0
        }
6492
402
        try {
6493
402
            const auto &row = res.front();
6494
402
            size_t idx = 0;
6495
402
            const auto &name = row[idx++];
6496
402
            const auto &description = row[idx++];
6497
402
            const auto &method_auth_name = row[idx++];
6498
402
            const auto &method_code = row[idx++];
6499
402
            const auto &method_name = row[idx++];
6500
402
            const auto &source_crs_auth_name = row[idx++];
6501
402
            const auto &source_crs_code = row[idx++];
6502
402
            const auto &target_crs_auth_name = row[idx++];
6503
402
            const auto &target_crs_code = row[idx++];
6504
402
            const auto &accuracy = row[idx++];
6505
402
            const auto &grid_param_auth_name = row[idx++];
6506
402
            const auto &grid_param_code = row[idx++];
6507
402
            const auto &grid_param_name = row[idx++];
6508
402
            const auto &grid_name = row[idx++];
6509
402
            const auto &grid2_param_auth_name = row[idx++];
6510
402
            const auto &grid2_param_code = row[idx++];
6511
402
            const auto &grid2_param_name = row[idx++];
6512
402
            const auto &grid2_name = row[idx++];
6513
402
            const auto &interpolation_crs_auth_name = row[idx++];
6514
402
            const auto &interpolation_crs_code = row[idx++];
6515
402
            const auto &operation_version = row[idx++];
6516
402
            const auto &deprecated_str = row[idx++];
6517
402
            const bool deprecated = deprecated_str == "1";
6518
402
            assert(idx == row.size());
6519
6520
402
            auto sourceCRS =
6521
402
                d->createFactory(source_crs_auth_name)
6522
402
                    ->createCoordinateReferenceSystem(source_crs_code);
6523
402
            auto targetCRS =
6524
402
                d->createFactory(target_crs_auth_name)
6525
402
                    ->createCoordinateReferenceSystem(target_crs_code);
6526
402
            auto interpolationCRS =
6527
402
                interpolation_crs_auth_name.empty()
6528
402
                    ? nullptr
6529
402
                    : d->createFactory(interpolation_crs_auth_name)
6530
119
                          ->createCoordinateReferenceSystem(
6531
119
                              interpolation_crs_code)
6532
119
                          .as_nullable();
6533
6534
402
            std::vector<operation::OperationParameterNNPtr> parameters;
6535
402
            std::vector<operation::ParameterValueNNPtr> values;
6536
6537
402
            parameters.emplace_back(operation::OperationParameter::create(
6538
402
                util::PropertyMap()
6539
402
                    .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
6540
402
                    .set(metadata::Identifier::CODESPACE_KEY,
6541
402
                         grid_param_auth_name)
6542
402
                    .set(metadata::Identifier::CODE_KEY, grid_param_code)));
6543
402
            values.emplace_back(
6544
402
                operation::ParameterValue::createFilename(grid_name));
6545
402
            if (!grid2_name.empty()) {
6546
52
                parameters.emplace_back(operation::OperationParameter::create(
6547
52
                    util::PropertyMap()
6548
52
                        .set(common::IdentifiedObject::NAME_KEY,
6549
52
                             grid2_param_name)
6550
52
                        .set(metadata::Identifier::CODESPACE_KEY,
6551
52
                             grid2_param_auth_name)
6552
52
                        .set(metadata::Identifier::CODE_KEY,
6553
52
                             grid2_param_code)));
6554
52
                values.emplace_back(
6555
52
                    operation::ParameterValue::createFilename(grid2_name));
6556
52
            }
6557
6558
402
            auto props = d->createPropertiesSearchUsages(
6559
402
                type, code, name, deprecated, description);
6560
402
            if (!operation_version.empty()) {
6561
397
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6562
397
                          operation_version);
6563
397
            }
6564
402
            auto propsMethod =
6565
402
                util::PropertyMap()
6566
402
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6567
402
                    .set(metadata::Identifier::CODE_KEY, method_code)
6568
402
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6569
6570
402
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6571
402
            if (!accuracy.empty() && accuracy != "999.0") {
6572
394
                accuracies.emplace_back(
6573
394
                    metadata::PositionalAccuracy::create(accuracy));
6574
394
            }
6575
6576
            // A bit fragile to detect the operation type with the method name,
6577
            // but not worth changing the database model
6578
402
            if (starts_with(method_name, "Point motion")) {
6579
7
                if (!sourceCRS->isEquivalentTo(targetCRS.get())) {
6580
0
                    throw operation::InvalidOperation(
6581
0
                        "source_crs and target_crs should be the same for a "
6582
0
                        "PointMotionOperation");
6583
0
                }
6584
6585
7
                auto pmo = operation::PointMotionOperation::create(
6586
7
                    props, sourceCRS, propsMethod, parameters, values,
6587
7
                    accuracies);
6588
7
                if (usePROJAlternativeGridNames) {
6589
7
                    return pmo->substitutePROJAlternativeGridNames(
6590
7
                        d->context());
6591
7
                }
6592
0
                return pmo;
6593
7
            }
6594
6595
395
            auto transf = operation::Transformation::create(
6596
395
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6597
395
                parameters, values, accuracies);
6598
395
            if (usePROJAlternativeGridNames) {
6599
395
                return transf->substitutePROJAlternativeGridNames(d->context());
6600
395
            }
6601
0
            return transf;
6602
6603
395
        } catch (const std::exception &ex) {
6604
0
            throw buildFactoryException("transformation", d->authority(), code,
6605
0
                                        ex);
6606
0
        }
6607
402
    }
6608
6609
262
    if (type == "other_transformation") {
6610
174
        std::ostringstream buffer;
6611
174
        buffer.imbue(std::locale::classic());
6612
174
        buffer
6613
174
            << "SELECT name, description, "
6614
174
               "method_auth_name, method_code, method_name, "
6615
174
               "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
6616
174
               "target_crs_code, "
6617
174
               "interpolation_crs_auth_name, interpolation_crs_code, "
6618
174
               "operation_version, accuracy, deprecated";
6619
1.39k
        for (size_t i = 1; i <= N_MAX_PARAMS; ++i) {
6620
1.21k
            buffer << ", param" << i << "_auth_name";
6621
1.21k
            buffer << ", param" << i << "_code";
6622
1.21k
            buffer << ", param" << i << "_name";
6623
1.21k
            buffer << ", param" << i << "_value";
6624
1.21k
            buffer << ", param" << i << "_uom_auth_name";
6625
1.21k
            buffer << ", param" << i << "_uom_code";
6626
1.21k
        }
6627
174
        buffer << " FROM other_transformation "
6628
174
                  "WHERE auth_name = ? AND code = ?";
6629
6630
174
        auto res = d->runWithCodeParam(buffer.str(), code);
6631
174
        if (res.empty()) {
6632
            // shouldn't happen if foreign keys are OK
6633
0
            throw NoSuchAuthorityCodeException("other_transformation not found",
6634
0
                                               d->authority(), code);
6635
0
        }
6636
174
        try {
6637
174
            const auto &row = res.front();
6638
174
            size_t idx = 0;
6639
174
            const auto &name = row[idx++];
6640
174
            const auto &description = row[idx++];
6641
174
            const auto &method_auth_name = row[idx++];
6642
174
            const auto &method_code = row[idx++];
6643
174
            const auto &method_name = row[idx++];
6644
174
            const auto &source_crs_auth_name = row[idx++];
6645
174
            const auto &source_crs_code = row[idx++];
6646
174
            const auto &target_crs_auth_name = row[idx++];
6647
174
            const auto &target_crs_code = row[idx++];
6648
174
            const auto &interpolation_crs_auth_name = row[idx++];
6649
174
            const auto &interpolation_crs_code = row[idx++];
6650
174
            const auto &operation_version = row[idx++];
6651
174
            const auto &accuracy = row[idx++];
6652
174
            const auto &deprecated_str = row[idx++];
6653
174
            const bool deprecated = deprecated_str == "1";
6654
6655
174
            const size_t base_param_idx = idx;
6656
174
            std::vector<operation::OperationParameterNNPtr> parameters;
6657
174
            std::vector<operation::ParameterValueNNPtr> values;
6658
544
            for (size_t i = 0; i < N_MAX_PARAMS; ++i) {
6659
544
                const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
6660
544
                if (param_auth_name.empty()) {
6661
174
                    break;
6662
174
                }
6663
370
                const auto &param_code = row[base_param_idx + i * 6 + 1];
6664
370
                const auto &param_name = row[base_param_idx + i * 6 + 2];
6665
370
                const auto &param_value = row[base_param_idx + i * 6 + 3];
6666
370
                const auto &param_uom_auth_name =
6667
370
                    row[base_param_idx + i * 6 + 4];
6668
370
                const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
6669
370
                parameters.emplace_back(operation::OperationParameter::create(
6670
370
                    util::PropertyMap()
6671
370
                        .set(metadata::Identifier::CODESPACE_KEY,
6672
370
                             param_auth_name)
6673
370
                        .set(metadata::Identifier::CODE_KEY, param_code)
6674
370
                        .set(common::IdentifiedObject::NAME_KEY, param_name)));
6675
370
                std::string normalized_uom_code(param_uom_code);
6676
370
                const double normalized_value = normalizeMeasure(
6677
370
                    param_uom_code, param_value, normalized_uom_code);
6678
370
                auto uom = d->createUnitOfMeasure(param_uom_auth_name,
6679
370
                                                  normalized_uom_code);
6680
370
                values.emplace_back(operation::ParameterValue::create(
6681
370
                    common::Measure(normalized_value, uom)));
6682
370
            }
6683
174
            idx = base_param_idx + 6 * N_MAX_PARAMS;
6684
174
            (void)idx;
6685
174
            assert(idx == row.size());
6686
6687
174
            auto sourceCRS =
6688
174
                d->createFactory(source_crs_auth_name)
6689
174
                    ->createCoordinateReferenceSystem(source_crs_code);
6690
174
            auto targetCRS =
6691
174
                d->createFactory(target_crs_auth_name)
6692
174
                    ->createCoordinateReferenceSystem(target_crs_code);
6693
174
            auto interpolationCRS =
6694
174
                interpolation_crs_auth_name.empty()
6695
174
                    ? nullptr
6696
174
                    : d->createFactory(interpolation_crs_auth_name)
6697
10
                          ->createCoordinateReferenceSystem(
6698
10
                              interpolation_crs_code)
6699
10
                          .as_nullable();
6700
6701
174
            auto props = d->createPropertiesSearchUsages(
6702
174
                type, code, name, deprecated, description);
6703
174
            if (!operation_version.empty()) {
6704
166
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6705
166
                          operation_version);
6706
166
            }
6707
6708
174
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6709
174
            if (!accuracy.empty() && accuracy != "999.0") {
6710
170
                accuracies.emplace_back(
6711
170
                    metadata::PositionalAccuracy::create(accuracy));
6712
170
            }
6713
6714
174
            if (method_auth_name == "PROJ") {
6715
19
                if (method_code == "PROJString") {
6716
19
                    auto op = operation::SingleOperation::createPROJBased(
6717
19
                        props, method_name, sourceCRS, targetCRS, accuracies);
6718
19
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6719
19
                    return op;
6720
19
                } else if (method_code == "WKT") {
6721
0
                    auto op = util::nn_dynamic_pointer_cast<
6722
0
                        operation::CoordinateOperation>(
6723
0
                        WKTParser().createFromWKT(method_name));
6724
0
                    if (!op) {
6725
0
                        throw FactoryException("WKT string does not express a "
6726
0
                                               "coordinate operation");
6727
0
                    }
6728
0
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6729
0
                    return NN_NO_CHECK(op);
6730
0
                }
6731
19
            }
6732
6733
155
            auto propsMethod =
6734
155
                util::PropertyMap()
6735
155
                    .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
6736
155
                    .set(metadata::Identifier::CODE_KEY, method_code)
6737
155
                    .set(common::IdentifiedObject::NAME_KEY, method_name);
6738
6739
155
            if (method_auth_name == metadata::Identifier::EPSG) {
6740
155
                int method_code_int = std::atoi(method_code.c_str());
6741
155
                if (operation::isAxisOrderReversal(method_code_int) ||
6742
155
                    method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
6743
155
                    method_code_int ==
6744
154
                        EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
6745
155
                    method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
6746
5
                    auto op = operation::Conversion::create(props, propsMethod,
6747
5
                                                            parameters, values);
6748
5
                    op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
6749
5
                    return op;
6750
5
                }
6751
155
            }
6752
150
            return operation::Transformation::create(
6753
150
                props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
6754
150
                parameters, values, accuracies);
6755
6756
155
        } catch (const std::exception &ex) {
6757
0
            throw buildFactoryException("transformation", d->authority(), code,
6758
0
                                        ex);
6759
0
        }
6760
174
    }
6761
6762
88
    if (allowConcatenated && type == "concatenated_operation") {
6763
88
        auto res = d->runWithCodeParam(
6764
88
            "SELECT name, description, "
6765
88
            "source_crs_auth_name, source_crs_code, "
6766
88
            "target_crs_auth_name, target_crs_code, "
6767
88
            "accuracy, "
6768
88
            "operation_version, deprecated FROM "
6769
88
            "concatenated_operation WHERE auth_name = ? AND code = ?",
6770
88
            code);
6771
88
        if (res.empty()) {
6772
            // shouldn't happen if foreign keys are OK
6773
0
            throw NoSuchAuthorityCodeException(
6774
0
                "concatenated_operation not found", d->authority(), code);
6775
0
        }
6776
6777
88
        auto resSteps = d->runWithCodeParam(
6778
88
            "SELECT step_auth_name, step_code, step_direction FROM "
6779
88
            "concatenated_operation_step WHERE operation_auth_name = ? "
6780
88
            "AND operation_code = ? ORDER BY step_number",
6781
88
            code);
6782
6783
88
        try {
6784
88
            const auto &row = res.front();
6785
88
            size_t idx = 0;
6786
88
            const auto &name = row[idx++];
6787
88
            const auto &description = row[idx++];
6788
88
            const auto &source_crs_auth_name = row[idx++];
6789
88
            const auto &source_crs_code = row[idx++];
6790
88
            const auto &target_crs_auth_name = row[idx++];
6791
88
            const auto &target_crs_code = row[idx++];
6792
88
            const auto &accuracy = row[idx++];
6793
88
            const auto &operation_version = row[idx++];
6794
88
            const auto &deprecated_str = row[idx++];
6795
88
            const bool deprecated = deprecated_str == "1";
6796
6797
88
            std::vector<operation::CoordinateOperationNNPtr> operations;
6798
88
            size_t countExplicitDirection = 0;
6799
226
            for (const auto &rowStep : resSteps) {
6800
226
                const auto &step_auth_name = rowStep[0];
6801
226
                const auto &step_code = rowStep[1];
6802
226
                const auto &step_direction = rowStep[2];
6803
226
                auto stepOp =
6804
226
                    d->createFactory(step_auth_name)
6805
226
                        ->createCoordinateOperation(step_code, false,
6806
226
                                                    usePROJAlternativeGridNames,
6807
226
                                                    std::string());
6808
226
                if (step_direction == "forward") {
6809
160
                    ++countExplicitDirection;
6810
160
                    operations.push_back(std::move(stepOp));
6811
160
                } else if (step_direction == "reverse") {
6812
8
                    ++countExplicitDirection;
6813
8
                    operations.push_back(stepOp->inverse());
6814
58
                } else {
6815
58
                    operations.push_back(std::move(stepOp));
6816
58
                }
6817
226
            }
6818
6819
88
            if (countExplicitDirection > 0 &&
6820
88
                countExplicitDirection != resSteps.size()) {
6821
0
                throw FactoryException("not all steps have a defined direction "
6822
0
                                       "for concatenated operation " +
6823
0
                                       code);
6824
0
            }
6825
6826
88
            const bool fixDirectionAllowed = (countExplicitDirection == 0);
6827
88
            operation::ConcatenatedOperation::fixSteps(
6828
88
                d->createFactory(source_crs_auth_name)
6829
88
                    ->createCoordinateReferenceSystem(source_crs_code),
6830
88
                d->createFactory(target_crs_auth_name)
6831
88
                    ->createCoordinateReferenceSystem(target_crs_code),
6832
88
                operations, d->context(), fixDirectionAllowed);
6833
6834
88
            auto props = d->createPropertiesSearchUsages(
6835
88
                type, code, name, deprecated, description);
6836
88
            if (!operation_version.empty()) {
6837
41
                props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
6838
41
                          operation_version);
6839
41
            }
6840
6841
88
            std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
6842
88
            if (!accuracy.empty()) {
6843
88
                if (accuracy != "999.0") {
6844
81
                    accuracies.emplace_back(
6845
81
                        metadata::PositionalAccuracy::create(accuracy));
6846
81
                }
6847
88
            } else {
6848
                // Try to compute a reasonable accuracy from the members
6849
0
                double totalAcc = -1;
6850
0
                try {
6851
0
                    for (const auto &op : operations) {
6852
0
                        auto accs = op->coordinateOperationAccuracies();
6853
0
                        if (accs.size() == 1) {
6854
0
                            double acc = c_locale_stod(accs[0]->value());
6855
0
                            if (totalAcc < 0) {
6856
0
                                totalAcc = acc;
6857
0
                            } else {
6858
0
                                totalAcc += acc;
6859
0
                            }
6860
0
                        } else if (dynamic_cast<const operation::Conversion *>(
6861
0
                                       op.get())) {
6862
                            // A conversion is perfectly accurate.
6863
0
                            if (totalAcc < 0) {
6864
0
                                totalAcc = 0;
6865
0
                            }
6866
0
                        } else {
6867
0
                            totalAcc = -1;
6868
0
                            break;
6869
0
                        }
6870
0
                    }
6871
0
                    if (totalAcc >= 0) {
6872
0
                        accuracies.emplace_back(
6873
0
                            metadata::PositionalAccuracy::create(
6874
0
                                toString(totalAcc)));
6875
0
                    }
6876
0
                } catch (const std::exception &) {
6877
0
                }
6878
0
            }
6879
88
            return operation::ConcatenatedOperation::create(props, operations,
6880
88
                                                            accuracies);
6881
6882
88
        } catch (const std::exception &ex) {
6883
0
            throw buildFactoryException("transformation", d->authority(), code,
6884
0
                                        ex);
6885
0
        }
6886
88
    }
6887
6888
0
    throw FactoryException("unhandled coordinate operation type: " + type);
6889
88
}
6890
6891
// ---------------------------------------------------------------------------
6892
6893
/** \brief Returns a list operation::CoordinateOperation between two CRS.
6894
 *
6895
 * The list is ordered with preferred operations first. No attempt is made
6896
 * at inferring operations that are not explicitly in the database.
6897
 *
6898
 * Deprecated operations are rejected.
6899
 *
6900
 * @param sourceCRSCode Source CRS code allocated by authority.
6901
 * @param targetCRSCode Source CRS code allocated by authority.
6902
 * @return list of coordinate operations
6903
 * @throw NoSuchAuthorityCodeException if there is no matching object.
6904
 * @throw FactoryException in case of other errors.
6905
 */
6906
6907
std::vector<operation::CoordinateOperationNNPtr>
6908
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
6909
0
    const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
6910
0
    return createFromCoordinateReferenceSystemCodes(
6911
0
        d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
6912
0
        false, false, false);
6913
0
}
6914
6915
// ---------------------------------------------------------------------------
6916
6917
/** \brief Returns a list of geoid models available for that crs
6918
 *
6919
 * The list includes the geoid models connected directly with the crs,
6920
 * or via "Height Depth Reversal" or "Change of Vertical Unit" transformations
6921
 *
6922
 * @param code crs code allocated by authority.
6923
 * @return list of geoid model names
6924
 * @throw FactoryException in case of error.
6925
 */
6926
6927
std::list<std::string>
6928
0
AuthorityFactory::getGeoidModels(const std::string &code) const {
6929
6930
0
    ListOfParams params;
6931
0
    std::string sql;
6932
0
    sql += "SELECT DISTINCT GM0.name "
6933
0
           "  FROM geoid_model GM0 "
6934
0
           "INNER JOIN grid_transformation GT0 "
6935
0
           "  ON  GT0.code = GM0.operation_code "
6936
0
           "  AND GT0.auth_name = GM0.operation_auth_name "
6937
0
           "  AND GT0.deprecated = 0 "
6938
0
           "INNER JOIN vertical_crs VC0 "
6939
0
           "  ON VC0.code = GT0.target_crs_code "
6940
0
           "  AND VC0.auth_name = GT0.target_crs_auth_name "
6941
0
           "INNER JOIN vertical_crs VC1 "
6942
0
           "  ON VC1.datum_code = VC0.datum_code "
6943
0
           "  AND VC1.datum_auth_name = VC0.datum_auth_name "
6944
0
           "  AND VC1.code = ? ";
6945
0
    params.emplace_back(code);
6946
0
    if (d->hasAuthorityRestriction()) {
6947
0
        sql += " AND GT0.target_crs_auth_name = ? ";
6948
0
        params.emplace_back(d->authority());
6949
0
    }
6950
0
    sql += " ORDER BY 1 ";
6951
6952
0
    auto sqlRes = d->run(sql, params);
6953
0
    std::list<std::string> res;
6954
0
    for (const auto &row : sqlRes) {
6955
0
        res.push_back(row[0]);
6956
0
    }
6957
0
    return res;
6958
0
}
6959
6960
// ---------------------------------------------------------------------------
6961
6962
/** \brief Returns a list operation::CoordinateOperation between two CRS.
6963
 *
6964
 * The list is ordered with preferred operations first. No attempt is made
6965
 * at inferring operations that are not explicitly in the database (see
6966
 * createFromCRSCodesWithIntermediates() for that), and only
6967
 * source -> target operations are searched (i.e. if target -> source is
6968
 * present, you need to call this method with the arguments reversed, and apply
6969
 * the reverse transformations).
6970
 *
6971
 * Deprecated operations are rejected.
6972
 *
6973
 * If getAuthority() returns empty, then coordinate operations from all
6974
 * authorities are considered.
6975
 *
6976
 * @param sourceCRSAuthName Authority name of sourceCRSCode
6977
 * @param sourceCRSCode Source CRS code allocated by authority
6978
 * sourceCRSAuthName.
6979
 * @param targetCRSAuthName Authority name of targetCRSCode
6980
 * @param targetCRSCode Source CRS code allocated by authority
6981
 * targetCRSAuthName.
6982
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
6983
 * should be substituted to the official grid names.
6984
 * @param discardIfMissingGrid Whether coordinate operations that reference
6985
 * missing grids should be removed from the result set.
6986
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
6987
 * as available (typically when network is enabled).
6988
 * @param discardSuperseded Whether coordinate operations that are superseded
6989
 * (but not deprecated) should be removed from the result set.
6990
 * @param tryReverseOrder whether to search in the reverse order too (and thus
6991
 * inverse results found that way)
6992
 * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
6993
 * intersectingExtent2 should be honored in a strict way.
6994
 * @param intersectingExtent1 Optional extent that the resulting operations
6995
 * must intersect.
6996
 * @param intersectingExtent2 Optional extent that the resulting operations
6997
 * must intersect.
6998
 * @return list of coordinate operations
6999
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7000
 * @throw FactoryException in case of other errors.
7001
 */
7002
7003
std::vector<operation::CoordinateOperationNNPtr>
7004
AuthorityFactory::createFromCoordinateReferenceSystemCodes(
7005
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7006
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7007
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7008
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7009
    bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
7010
    const metadata::ExtentPtr &intersectingExtent1,
7011
0
    const metadata::ExtentPtr &intersectingExtent2) const {
7012
7013
0
    auto cacheKey(d->authority());
7014
0
    cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
7015
0
    cacheKey += sourceCRSCode;
7016
0
    cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
7017
0
    cacheKey += targetCRSCode;
7018
0
    cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
7019
0
    cacheKey += (discardIfMissingGrid ? '1' : '0');
7020
0
    cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
7021
0
    cacheKey += (discardSuperseded ? '1' : '0');
7022
0
    cacheKey += (tryReverseOrder ? '1' : '0');
7023
0
    cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
7024
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7025
0
        if (extent) {
7026
0
            const auto &geogExtent = extent->geographicElements();
7027
0
            if (geogExtent.size() == 1) {
7028
0
                auto bbox =
7029
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7030
0
                        geogExtent[0].get());
7031
0
                if (bbox) {
7032
0
                    cacheKey += toString(bbox->southBoundLatitude());
7033
0
                    cacheKey += toString(bbox->westBoundLongitude());
7034
0
                    cacheKey += toString(bbox->northBoundLatitude());
7035
0
                    cacheKey += toString(bbox->eastBoundLongitude());
7036
0
                }
7037
0
            }
7038
0
        }
7039
0
    }
7040
7041
0
    std::vector<operation::CoordinateOperationNNPtr> list;
7042
7043
0
    if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
7044
0
        return list;
7045
0
    }
7046
7047
    // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
7048
    // In which case use the conversion of the ProjectedCRS
7049
0
    if (!targetCRSAuthName.empty()) {
7050
0
        auto targetFactory = d->createFactory(targetCRSAuthName);
7051
0
        const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
7052
0
                                        targetCRSCode);
7053
0
        auto crs = targetFactory->d->context()->d->getCRSFromCache(
7054
0
            cacheKeyProjectedCRS);
7055
0
        crs::ProjectedCRSPtr targetProjCRS;
7056
0
        if (crs) {
7057
0
            targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
7058
0
        } else {
7059
0
            const auto sqlRes =
7060
0
                targetFactory->d->createProjectedCRSBegin(targetCRSCode);
7061
0
            if (!sqlRes.empty()) {
7062
0
                try {
7063
0
                    targetProjCRS =
7064
0
                        targetFactory->d
7065
0
                            ->createProjectedCRSEnd(targetCRSCode, sqlRes)
7066
0
                            .as_nullable();
7067
0
                } catch (const std::exception &) {
7068
0
                }
7069
0
            }
7070
0
        }
7071
0
        if (targetProjCRS) {
7072
0
            const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
7073
0
            if (sourceCRSAuthName.empty() ||
7074
0
                (!baseIds.empty() &&
7075
0
                 *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
7076
0
                 baseIds.front()->code() == sourceCRSCode)) {
7077
0
                bool ok = true;
7078
0
                auto conv = targetProjCRS->derivingConversion();
7079
0
                if (d->hasAuthorityRestriction()) {
7080
0
                    ok = *(conv->identifiers().front()->codeSpace()) ==
7081
0
                         d->authority();
7082
0
                }
7083
0
                if (ok) {
7084
0
                    list.emplace_back(conv);
7085
0
                    d->context()->d->cache(cacheKey, list);
7086
0
                    return list;
7087
0
                }
7088
0
            }
7089
0
        }
7090
0
    }
7091
7092
0
    std::string sql;
7093
0
    if (discardSuperseded) {
7094
0
        sql = "SELECT cov.source_crs_auth_name, cov.source_crs_code, "
7095
0
              "cov.target_crs_auth_name, cov.target_crs_code, "
7096
0
              "cov.auth_name, cov.code, cov.table_name, "
7097
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7098
0
              "extent.east_lon, "
7099
0
              "ss.replacement_auth_name, ss.replacement_code, "
7100
0
              "(gt.auth_name IS NOT NULL) AS replacement_is_grid_transform, "
7101
0
              "(ga.proj_grid_name IS NOT NULL) AS replacement_is_known_grid "
7102
0
              "FROM "
7103
0
              "coordinate_operation_view cov "
7104
0
              "JOIN usage ON "
7105
0
              "usage.object_table_name = cov.table_name AND "
7106
0
              "usage.object_auth_name = cov.auth_name AND "
7107
0
              "usage.object_code = cov.code "
7108
0
              "JOIN extent "
7109
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7110
0
              "extent.code = usage.extent_code "
7111
0
              "LEFT JOIN supersession ss ON "
7112
0
              "ss.superseded_table_name = cov.table_name AND "
7113
0
              "ss.superseded_auth_name = cov.auth_name AND "
7114
0
              "ss.superseded_code = cov.code AND "
7115
0
              "ss.superseded_table_name = ss.replacement_table_name AND "
7116
0
              "ss.same_source_target_crs = 1 "
7117
0
              "LEFT JOIN grid_transformation gt ON "
7118
0
              "gt.auth_name = ss.replacement_auth_name AND "
7119
0
              "gt.code = ss.replacement_code "
7120
0
              "LEFT JOIN grid_alternatives ga ON "
7121
0
              "ga.original_grid_name = gt.grid_name "
7122
0
              "WHERE ";
7123
0
    } else {
7124
0
        sql = "SELECT source_crs_auth_name, source_crs_code, "
7125
0
              "target_crs_auth_name, target_crs_code, "
7126
0
              "cov.auth_name, cov.code, cov.table_name, "
7127
0
              "extent.south_lat, extent.west_lon, extent.north_lat, "
7128
0
              "extent.east_lon "
7129
0
              "FROM "
7130
0
              "coordinate_operation_view cov "
7131
0
              "JOIN usage ON "
7132
0
              "usage.object_table_name = cov.table_name AND "
7133
0
              "usage.object_auth_name = cov.auth_name AND "
7134
0
              "usage.object_code = cov.code "
7135
0
              "JOIN extent "
7136
0
              "ON extent.auth_name = usage.extent_auth_name AND "
7137
0
              "extent.code = usage.extent_code "
7138
0
              "WHERE ";
7139
0
    }
7140
0
    ListOfParams params;
7141
0
    if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
7142
0
        if (tryReverseOrder) {
7143
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7144
0
                   "AND "
7145
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?) "
7146
0
                   "OR "
7147
0
                   "(cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7148
0
                   "AND "
7149
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ?)) "
7150
0
                   "AND ";
7151
0
            params.emplace_back(sourceCRSAuthName);
7152
0
            params.emplace_back(sourceCRSCode);
7153
0
            params.emplace_back(targetCRSAuthName);
7154
0
            params.emplace_back(targetCRSCode);
7155
0
            params.emplace_back(targetCRSAuthName);
7156
0
            params.emplace_back(targetCRSCode);
7157
0
            params.emplace_back(sourceCRSAuthName);
7158
0
            params.emplace_back(sourceCRSCode);
7159
0
        } else {
7160
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7161
0
                   "AND "
7162
0
                   "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7163
0
                   "AND ";
7164
0
            params.emplace_back(sourceCRSAuthName);
7165
0
            params.emplace_back(sourceCRSCode);
7166
0
            params.emplace_back(targetCRSAuthName);
7167
0
            params.emplace_back(targetCRSCode);
7168
0
        }
7169
0
    } else if (!sourceCRSAuthName.empty()) {
7170
0
        if (tryReverseOrder) {
7171
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7172
0
                   ")OR "
7173
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7174
0
                   " AND ";
7175
0
            params.emplace_back(sourceCRSAuthName);
7176
0
            params.emplace_back(sourceCRSCode);
7177
0
            params.emplace_back(sourceCRSAuthName);
7178
0
            params.emplace_back(sourceCRSCode);
7179
0
        } else {
7180
0
            sql += "cov.source_crs_auth_name = ? AND cov.source_crs_code = ? "
7181
0
                   "AND ";
7182
0
            params.emplace_back(sourceCRSAuthName);
7183
0
            params.emplace_back(sourceCRSCode);
7184
0
        }
7185
0
    } else if (!targetCRSAuthName.empty()) {
7186
0
        if (tryReverseOrder) {
7187
0
            sql += "((cov.source_crs_auth_name = ? AND cov.source_crs_code = ?)"
7188
0
                   " OR "
7189
0
                   "(cov.target_crs_auth_name = ? AND cov.target_crs_code = ?))"
7190
0
                   " AND ";
7191
0
            params.emplace_back(targetCRSAuthName);
7192
0
            params.emplace_back(targetCRSCode);
7193
0
            params.emplace_back(targetCRSAuthName);
7194
0
            params.emplace_back(targetCRSCode);
7195
0
        } else {
7196
0
            sql += "cov.target_crs_auth_name = ? AND cov.target_crs_code = ? "
7197
0
                   "AND ";
7198
0
            params.emplace_back(targetCRSAuthName);
7199
0
            params.emplace_back(targetCRSCode);
7200
0
        }
7201
0
    }
7202
0
    sql += "cov.deprecated = 0";
7203
0
    if (d->hasAuthorityRestriction()) {
7204
0
        sql += " AND cov.auth_name = ?";
7205
0
        params.emplace_back(d->authority());
7206
0
    }
7207
0
    sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
7208
0
           "east_lon) DESC, "
7209
0
           "(CASE WHEN cov.accuracy is NULL THEN 1 ELSE 0 END), cov.accuracy";
7210
0
    auto res = d->run(sql, params);
7211
0
    std::set<std::pair<std::string, std::string>> setTransf;
7212
0
    if (discardSuperseded) {
7213
0
        for (const auto &row : res) {
7214
0
            const auto &auth_name = row[4];
7215
0
            const auto &code = row[5];
7216
0
            setTransf.insert(
7217
0
                std::pair<std::string, std::string>(auth_name, code));
7218
0
        }
7219
0
    }
7220
7221
    // Do a pass to determine if there are transformations that intersect
7222
    // intersectingExtent1 & intersectingExtent2
7223
0
    std::vector<bool> intersectingTransformations;
7224
0
    intersectingTransformations.resize(res.size());
7225
0
    bool hasIntersectingTransformations = false;
7226
0
    size_t i = 0;
7227
0
    for (const auto &row : res) {
7228
0
        size_t thisI = i;
7229
0
        ++i;
7230
0
        if (discardSuperseded) {
7231
0
            const auto &replacement_auth_name = row[11];
7232
0
            const auto &replacement_code = row[12];
7233
0
            const bool replacement_is_grid_transform = row[13] == "1";
7234
0
            const bool replacement_is_known_grid = row[14] == "1";
7235
0
            if (!replacement_auth_name.empty() &&
7236
                // Ignore supersession if the replacement uses a unknown grid
7237
0
                !(replacement_is_grid_transform &&
7238
0
                  !replacement_is_known_grid) &&
7239
0
                setTransf.find(std::pair<std::string, std::string>(
7240
0
                    replacement_auth_name, replacement_code)) !=
7241
0
                    setTransf.end()) {
7242
                // Skip transformations that are superseded by others that got
7243
                // returned in the result set.
7244
0
                continue;
7245
0
            }
7246
0
        }
7247
7248
0
        bool intersecting = true;
7249
0
        try {
7250
0
            double south_lat = c_locale_stod(row[7]);
7251
0
            double west_lon = c_locale_stod(row[8]);
7252
0
            double north_lat = c_locale_stod(row[9]);
7253
0
            double east_lon = c_locale_stod(row[10]);
7254
0
            auto transf_extent = metadata::Extent::createFromBBOX(
7255
0
                west_lon, south_lat, east_lon, north_lat);
7256
7257
0
            for (const auto &extent :
7258
0
                 {intersectingExtent1, intersectingExtent2}) {
7259
0
                if (extent) {
7260
0
                    if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
7261
0
                        intersecting = false;
7262
0
                        break;
7263
0
                    }
7264
0
                }
7265
0
            }
7266
0
        } catch (const std::exception &) {
7267
0
        }
7268
7269
0
        intersectingTransformations[thisI] = intersecting;
7270
0
        if (intersecting)
7271
0
            hasIntersectingTransformations = true;
7272
0
    }
7273
7274
    // If there are intersecting transformations, then only report those ones
7275
    // If there are no intersecting transformations, report all of them
7276
    // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
7277
    // still want to be able to use the Pulkovo datum shift if EPSG:32631
7278
    // coordinates are used
7279
0
    i = 0;
7280
0
    for (const auto &row : res) {
7281
0
        size_t thisI = i;
7282
0
        ++i;
7283
0
        if ((hasIntersectingTransformations ||
7284
0
             reportOnlyIntersectingTransformations) &&
7285
0
            !intersectingTransformations[thisI]) {
7286
0
            continue;
7287
0
        }
7288
0
        if (discardSuperseded) {
7289
0
            const auto &replacement_auth_name = row[11];
7290
0
            const auto &replacement_code = row[12];
7291
0
            const bool replacement_is_grid_transform = row[13] == "1";
7292
0
            const bool replacement_is_known_grid = row[14] == "1";
7293
0
            if (!replacement_auth_name.empty() &&
7294
                // Ignore supersession if the replacement uses a unknown grid
7295
0
                !(replacement_is_grid_transform &&
7296
0
                  !replacement_is_known_grid) &&
7297
0
                setTransf.find(std::pair<std::string, std::string>(
7298
0
                    replacement_auth_name, replacement_code)) !=
7299
0
                    setTransf.end()) {
7300
                // Skip transformations that are superseded by others that got
7301
                // returned in the result set.
7302
0
                continue;
7303
0
            }
7304
0
        }
7305
7306
0
        const auto &source_crs_auth_name = row[0];
7307
0
        const auto &source_crs_code = row[1];
7308
0
        const auto &target_crs_auth_name = row[2];
7309
0
        const auto &target_crs_code = row[3];
7310
0
        const auto &auth_name = row[4];
7311
0
        const auto &code = row[5];
7312
0
        const auto &table_name = row[6];
7313
0
        try {
7314
0
            auto op = d->createFactory(auth_name)->createCoordinateOperation(
7315
0
                code, true, usePROJAlternativeGridNames, table_name);
7316
0
            if (tryReverseOrder &&
7317
0
                (!sourceCRSAuthName.empty()
7318
0
                     ? (source_crs_auth_name != sourceCRSAuthName ||
7319
0
                        source_crs_code != sourceCRSCode)
7320
0
                     : (target_crs_auth_name != targetCRSAuthName ||
7321
0
                        target_crs_code != targetCRSCode))) {
7322
0
                op = op->inverse();
7323
0
            }
7324
0
            if (!discardIfMissingGrid ||
7325
0
                !d->rejectOpDueToMissingGrid(op,
7326
0
                                             considerKnownGridsAsAvailable)) {
7327
0
                list.emplace_back(op);
7328
0
            }
7329
0
        } catch (const std::exception &e) {
7330
            // Mostly for debugging purposes when using an inconsistent
7331
            // database
7332
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7333
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7334
0
            } else {
7335
0
                throw;
7336
0
            }
7337
0
        }
7338
0
    }
7339
0
    d->context()->d->cache(cacheKey, list);
7340
0
    return list;
7341
0
}
7342
7343
// ---------------------------------------------------------------------------
7344
7345
//! @cond Doxygen_Suppress
7346
static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
7347
                               const std::string &sourceCRSAuthName,
7348
                               const std::string &sourceCRSCode,
7349
                               const std::string &targetCRSAuthName,
7350
0
                               const std::string &targetCRSCode) {
7351
0
    auto concat =
7352
0
        dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
7353
0
    if (!concat) {
7354
0
        return false;
7355
0
    }
7356
0
    auto ops = concat->operations();
7357
0
    for (size_t i = 0; i + 1 < ops.size(); i++) {
7358
0
        auto targetCRS = ops[i]->targetCRS();
7359
0
        if (targetCRS) {
7360
0
            const auto &ids = targetCRS->identifiers();
7361
0
            if (ids.size() == 1 &&
7362
0
                ((*ids[0]->codeSpace() == sourceCRSAuthName &&
7363
0
                  ids[0]->code() == sourceCRSCode) ||
7364
0
                 (*ids[0]->codeSpace() == targetCRSAuthName &&
7365
0
                  ids[0]->code() == targetCRSCode))) {
7366
0
                return true;
7367
0
            }
7368
0
        }
7369
0
    }
7370
0
    return false;
7371
0
}
7372
//! @endcond
7373
7374
// ---------------------------------------------------------------------------
7375
7376
/** \brief Returns a list operation::CoordinateOperation between two CRS,
7377
 * using intermediate codes.
7378
 *
7379
 * The list is ordered with preferred operations first.
7380
 *
7381
 * Deprecated operations are rejected.
7382
 *
7383
 * The method will take care of considering all potential combinations (i.e.
7384
 * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
7385
 * call it with sourceCRS and targetCRS switched)
7386
 *
7387
 * If getAuthority() returns empty, then coordinate operations from all
7388
 * authorities are considered.
7389
 *
7390
 * @param sourceCRSAuthName Authority name of sourceCRSCode
7391
 * @param sourceCRSCode Source CRS code allocated by authority
7392
 * sourceCRSAuthName.
7393
 * @param targetCRSAuthName Authority name of targetCRSCode
7394
 * @param targetCRSCode Source CRS code allocated by authority
7395
 * targetCRSAuthName.
7396
 * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
7397
 * should be substituted to the official grid names.
7398
 * @param discardIfMissingGrid Whether coordinate operations that reference
7399
 * missing grids should be removed from the result set.
7400
 * @param considerKnownGridsAsAvailable Whether known grids should be considered
7401
 * as available (typically when network is enabled).
7402
 * @param discardSuperseded Whether coordinate operations that are superseded
7403
 * (but not deprecated) should be removed from the result set.
7404
 * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
7405
 * used as potential intermediate CRS. If the list is empty, the database will
7406
 * be used to find common CRS in operations involving both the source and
7407
 * target CRS.
7408
 * @param allowedIntermediateObjectType Restrict the type of the intermediate
7409
 * object considered.
7410
 * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
7411
 * @param allowedAuthorities One or several authority name allowed for the two
7412
 * coordinate operations that are going to be searched. When this vector is
7413
 * no empty, it overrides the authority of this object. This is useful for
7414
 * example when the coordinate operations to chain belong to two different
7415
 * allowed authorities.
7416
 * @param intersectingExtent1 Optional extent that the resulting operations
7417
 * must intersect.
7418
 * @param intersectingExtent2 Optional extent that the resulting operations
7419
 * must intersect.
7420
 * @return list of coordinate operations
7421
 * @throw NoSuchAuthorityCodeException if there is no matching object.
7422
 * @throw FactoryException in case of other errors.
7423
 */
7424
7425
std::vector<operation::CoordinateOperationNNPtr>
7426
AuthorityFactory::createFromCRSCodesWithIntermediates(
7427
    const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
7428
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
7429
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
7430
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
7431
    const std::vector<std::pair<std::string, std::string>>
7432
        &intermediateCRSAuthCodes,
7433
    ObjectType allowedIntermediateObjectType,
7434
    const std::vector<std::string> &allowedAuthorities,
7435
    const metadata::ExtentPtr &intersectingExtent1,
7436
0
    const metadata::ExtentPtr &intersectingExtent2) const {
7437
7438
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
7439
7440
0
    if (sourceCRSAuthName == targetCRSAuthName &&
7441
0
        sourceCRSCode == targetCRSCode) {
7442
0
        return listTmp;
7443
0
    }
7444
7445
0
    const auto CheckIfHasOperations = [this](const std::string &auth_name,
7446
0
                                             const std::string &code) {
7447
0
        return !(d->run("SELECT 1 FROM coordinate_operation_view WHERE "
7448
0
                        "(source_crs_auth_name = ? AND source_crs_code = ?) OR "
7449
0
                        "(target_crs_auth_name = ? AND target_crs_code = ?) "
7450
0
                        "LIMIT 1",
7451
0
                        {auth_name, code, auth_name, code})
7452
0
                     .empty());
7453
0
    };
7454
7455
    // If the source or target CRS are not the source or target of an operation,
7456
    // do not run the next costly requests.
7457
0
    if (!CheckIfHasOperations(sourceCRSAuthName, sourceCRSCode) ||
7458
0
        !CheckIfHasOperations(targetCRSAuthName, targetCRSCode)) {
7459
0
        return listTmp;
7460
0
    }
7461
7462
0
    const std::string sqlProlog(
7463
0
        discardSuperseded
7464
0
            ?
7465
7466
0
            "SELECT v1.table_name as table1, "
7467
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7468
0
            "v1.accuracy AS accuracy1, "
7469
0
            "v2.table_name as table2, "
7470
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7471
0
            "v2.accuracy as accuracy2, "
7472
0
            "a1.south_lat AS south_lat1, "
7473
0
            "a1.west_lon AS west_lon1, "
7474
0
            "a1.north_lat AS north_lat1, "
7475
0
            "a1.east_lon AS east_lon1, "
7476
0
            "a2.south_lat AS south_lat2, "
7477
0
            "a2.west_lon AS west_lon2, "
7478
0
            "a2.north_lat AS north_lat2, "
7479
0
            "a2.east_lon AS east_lon2, "
7480
0
            "ss1.replacement_auth_name AS replacement_auth_name1, "
7481
0
            "ss1.replacement_code AS replacement_code1, "
7482
0
            "ss2.replacement_auth_name AS replacement_auth_name2, "
7483
0
            "ss2.replacement_code AS replacement_code2 "
7484
0
            "FROM coordinate_operation_view v1 "
7485
0
            "JOIN coordinate_operation_view v2 "
7486
0
            :
7487
7488
0
            "SELECT v1.table_name as table1, "
7489
0
            "v1.auth_name AS auth_name1, v1.code AS code1, "
7490
0
            "v1.accuracy AS accuracy1, "
7491
0
            "v2.table_name as table2, "
7492
0
            "v2.auth_name AS auth_name2, v2.code AS code2, "
7493
0
            "v2.accuracy as accuracy2, "
7494
0
            "a1.south_lat AS south_lat1, "
7495
0
            "a1.west_lon AS west_lon1, "
7496
0
            "a1.north_lat AS north_lat1, "
7497
0
            "a1.east_lon AS east_lon1, "
7498
0
            "a2.south_lat AS south_lat2, "
7499
0
            "a2.west_lon AS west_lon2, "
7500
0
            "a2.north_lat AS north_lat2, "
7501
0
            "a2.east_lon AS east_lon2 "
7502
0
            "FROM coordinate_operation_view v1 "
7503
0
            "JOIN coordinate_operation_view v2 ");
7504
7505
0
    const char *joinSupersession =
7506
0
        "LEFT JOIN supersession ss1 ON "
7507
0
        "ss1.superseded_table_name = v1.table_name AND "
7508
0
        "ss1.superseded_auth_name = v1.auth_name AND "
7509
0
        "ss1.superseded_code = v1.code AND "
7510
0
        "ss1.superseded_table_name = ss1.replacement_table_name AND "
7511
0
        "ss1.same_source_target_crs = 1 "
7512
0
        "LEFT JOIN supersession ss2 ON "
7513
0
        "ss2.superseded_table_name = v2.table_name AND "
7514
0
        "ss2.superseded_auth_name = v2.auth_name AND "
7515
0
        "ss2.superseded_code = v2.code AND "
7516
0
        "ss2.superseded_table_name = ss2.replacement_table_name AND "
7517
0
        "ss2.same_source_target_crs = 1 ";
7518
0
    const std::string joinArea(
7519
0
        (discardSuperseded ? std::string(joinSupersession) : std::string())
7520
0
            .append("JOIN usage u1 ON "
7521
0
                    "u1.object_table_name = v1.table_name AND "
7522
0
                    "u1.object_auth_name = v1.auth_name AND "
7523
0
                    "u1.object_code = v1.code "
7524
0
                    "JOIN extent a1 "
7525
0
                    "ON a1.auth_name = u1.extent_auth_name AND "
7526
0
                    "a1.code = u1.extent_code "
7527
0
                    "JOIN usage u2 ON "
7528
0
                    "u2.object_table_name = v2.table_name AND "
7529
0
                    "u2.object_auth_name = v2.auth_name AND "
7530
0
                    "u2.object_code = v2.code "
7531
0
                    "JOIN extent a2 "
7532
0
                    "ON a2.auth_name = u2.extent_auth_name AND "
7533
0
                    "a2.code = u2.extent_code "));
7534
0
    const std::string orderBy(
7535
0
        "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
7536
0
        "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
7537
0
        "accuracy1 + accuracy2");
7538
7539
    // Case (source->intermediate) and (intermediate->target)
7540
0
    std::string sql(
7541
0
        sqlProlog +
7542
0
        "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
7543
0
        "AND v1.target_crs_code = v2.source_crs_code " +
7544
0
        joinArea +
7545
0
        "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7546
0
        "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
7547
0
    std::string minDate;
7548
0
    std::string criterionOnIntermediateCRS;
7549
7550
0
    const auto sourceCRS = d->createFactory(sourceCRSAuthName)
7551
0
                               ->createCoordinateReferenceSystem(sourceCRSCode);
7552
0
    const auto targetCRS = d->createFactory(targetCRSAuthName)
7553
0
                               ->createCoordinateReferenceSystem(targetCRSCode);
7554
7555
0
    const bool ETRFtoETRF = starts_with(sourceCRS->nameStr(), "ETRF") &&
7556
0
                            starts_with(targetCRS->nameStr(), "ETRF");
7557
7558
0
    const bool NAD83_CSRS_to_NAD83_CSRS =
7559
0
        starts_with(sourceCRS->nameStr(), "NAD83(CSRS)") &&
7560
0
        starts_with(targetCRS->nameStr(), "NAD83(CSRS)");
7561
7562
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7563
0
        const auto &sourceGeogCRS =
7564
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS.get());
7565
0
        const auto &targetGeogCRS =
7566
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS.get());
7567
0
        if (sourceGeogCRS && targetGeogCRS) {
7568
0
            const auto &sourceDatum = sourceGeogCRS->datum();
7569
0
            const auto &targetDatum = targetGeogCRS->datum();
7570
0
            if (sourceDatum && sourceDatum->publicationDate().has_value() &&
7571
0
                targetDatum && targetDatum->publicationDate().has_value()) {
7572
0
                const auto sourceDate(
7573
0
                    sourceDatum->publicationDate()->toString());
7574
0
                const auto targetDate(
7575
0
                    targetDatum->publicationDate()->toString());
7576
0
                minDate = std::min(sourceDate, targetDate);
7577
                // Check that the datum of the intermediateCRS has a publication
7578
                // date most recent that the one of the source and the target
7579
                // CRS Except when using the usual WGS84 pivot which happens to
7580
                // have a NULL publication date.
7581
0
                criterionOnIntermediateCRS =
7582
0
                    "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7583
0
                    "JOIN geodetic_datum y "
7584
0
                    "ON "
7585
0
                    "y.auth_name = x.datum_auth_name AND "
7586
0
                    "y.code = x.datum_code "
7587
0
                    "WHERE "
7588
0
                    "x.auth_name = v1.target_crs_auth_name AND "
7589
0
                    "x.code = v1.target_crs_code AND "
7590
0
                    "x.type IN ('geographic 2D', 'geographic 3D') AND "
7591
0
                    "(y.publication_date IS NULL OR "
7592
0
                    "(y.publication_date >= '" +
7593
0
                    minDate + "'))) ";
7594
0
            }
7595
0
        }
7596
0
        if (criterionOnIntermediateCRS.empty()) {
7597
0
            criterionOnIntermediateCRS =
7598
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7599
0
                "x.auth_name = v1.target_crs_auth_name AND "
7600
0
                "x.code = v1.target_crs_code AND "
7601
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7602
0
        }
7603
0
        sql += criterionOnIntermediateCRS;
7604
0
    }
7605
0
    auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
7606
0
                               targetCRSAuthName, targetCRSCode};
7607
0
    std::string additionalWhere(
7608
0
        "AND v1.deprecated = 0 AND v2.deprecated = 0 "
7609
0
        "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, "
7610
0
        "south_lat2, west_lon2, north_lat2, east_lon2) = 1 ");
7611
0
    if (!allowedAuthorities.empty()) {
7612
0
        additionalWhere += "AND v1.auth_name IN (";
7613
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7614
0
            if (i > 0)
7615
0
                additionalWhere += ',';
7616
0
            additionalWhere += '?';
7617
0
        }
7618
0
        additionalWhere += ") AND v2.auth_name IN (";
7619
0
        for (size_t i = 0; i < allowedAuthorities.size(); i++) {
7620
0
            if (i > 0)
7621
0
                additionalWhere += ',';
7622
0
            additionalWhere += '?';
7623
0
        }
7624
0
        additionalWhere += ')';
7625
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7626
0
            params.emplace_back(allowedAuthority);
7627
0
        }
7628
0
        for (const auto &allowedAuthority : allowedAuthorities) {
7629
0
            params.emplace_back(allowedAuthority);
7630
0
        }
7631
0
    }
7632
0
    if (d->hasAuthorityRestriction()) {
7633
0
        additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
7634
0
        params.emplace_back(d->authority());
7635
0
        params.emplace_back(d->authority());
7636
0
    }
7637
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
7638
0
        if (extent) {
7639
0
            const auto &geogExtent = extent->geographicElements();
7640
0
            if (geogExtent.size() == 1) {
7641
0
                auto bbox =
7642
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
7643
0
                        geogExtent[0].get());
7644
0
                if (bbox) {
7645
0
                    const double south_lat = bbox->southBoundLatitude();
7646
0
                    const double west_lon = bbox->westBoundLongitude();
7647
0
                    const double north_lat = bbox->northBoundLatitude();
7648
0
                    const double east_lon = bbox->eastBoundLongitude();
7649
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
7650
0
                        north_lat != 90.0 || east_lon != 180.0) {
7651
0
                        additionalWhere +=
7652
0
                            "AND intersects_bbox(south_lat1, "
7653
0
                            "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
7654
0
                            "intersects_bbox(south_lat2, west_lon2, "
7655
0
                            "north_lat2, east_lon2, ?, ?, ?, ?) ";
7656
0
                        params.emplace_back(south_lat);
7657
0
                        params.emplace_back(west_lon);
7658
0
                        params.emplace_back(north_lat);
7659
0
                        params.emplace_back(east_lon);
7660
0
                        params.emplace_back(south_lat);
7661
0
                        params.emplace_back(west_lon);
7662
0
                        params.emplace_back(north_lat);
7663
0
                        params.emplace_back(east_lon);
7664
0
                    }
7665
0
                }
7666
0
            }
7667
0
        }
7668
0
    }
7669
7670
0
    const auto buildIntermediateWhere =
7671
0
        [&intermediateCRSAuthCodes](const std::string &first_field,
7672
0
                                    const std::string &second_field) {
7673
0
            if (intermediateCRSAuthCodes.empty()) {
7674
0
                return std::string();
7675
0
            }
7676
0
            std::string l_sql(" AND (");
7677
0
            for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
7678
0
                if (i > 0) {
7679
0
                    l_sql += " OR";
7680
0
                }
7681
0
                l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
7682
0
                l_sql += "v1." + first_field + "_crs_code = ? AND ";
7683
0
                l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
7684
0
                l_sql += "v2." + second_field + "_crs_code = ?) ";
7685
0
            }
7686
0
            l_sql += ')';
7687
0
            return l_sql;
7688
0
        };
7689
7690
0
    std::string intermediateWhere = buildIntermediateWhere("target", "source");
7691
0
    for (const auto &pair : intermediateCRSAuthCodes) {
7692
0
        params.emplace_back(pair.first);
7693
0
        params.emplace_back(pair.second);
7694
0
        params.emplace_back(pair.first);
7695
0
        params.emplace_back(pair.second);
7696
0
    }
7697
0
    auto res =
7698
0
        d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7699
7700
0
    const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
7701
0
        std::set<std::pair<std::string, std::string>> setTransf1;
7702
0
        std::set<std::pair<std::string, std::string>> setTransf2;
7703
0
        for (const auto &row : resultSet) {
7704
            // table1
7705
0
            const auto &auth_name1 = row[1];
7706
0
            const auto &code1 = row[2];
7707
            // accuracy1
7708
            // table2
7709
0
            const auto &auth_name2 = row[5];
7710
0
            const auto &code2 = row[6];
7711
0
            setTransf1.insert(
7712
0
                std::pair<std::string, std::string>(auth_name1, code1));
7713
0
            setTransf2.insert(
7714
0
                std::pair<std::string, std::string>(auth_name2, code2));
7715
0
        }
7716
0
        SQLResultSet filteredResultSet;
7717
0
        for (const auto &row : resultSet) {
7718
0
            const auto &replacement_auth_name1 = row[16];
7719
0
            const auto &replacement_code1 = row[17];
7720
0
            const auto &replacement_auth_name2 = row[18];
7721
0
            const auto &replacement_code2 = row[19];
7722
0
            if (!replacement_auth_name1.empty() &&
7723
0
                setTransf1.find(std::pair<std::string, std::string>(
7724
0
                    replacement_auth_name1, replacement_code1)) !=
7725
0
                    setTransf1.end()) {
7726
                // Skip transformations that are superseded by others that got
7727
                // returned in the result set.
7728
0
                continue;
7729
0
            }
7730
0
            if (!replacement_auth_name2.empty() &&
7731
0
                setTransf2.find(std::pair<std::string, std::string>(
7732
0
                    replacement_auth_name2, replacement_code2)) !=
7733
0
                    setTransf2.end()) {
7734
                // Skip transformations that are superseded by others that got
7735
                // returned in the result set.
7736
0
                continue;
7737
0
            }
7738
0
            filteredResultSet.emplace_back(row);
7739
0
        }
7740
0
        return filteredResultSet;
7741
0
    };
7742
7743
0
    if (discardSuperseded) {
7744
0
        res = filterOutSuperseded(std::move(res));
7745
0
    }
7746
7747
0
    const auto checkPivot = [ETRFtoETRF, NAD83_CSRS_to_NAD83_CSRS, &sourceCRS,
7748
0
                             &targetCRS](const crs::CRSPtr &intermediateCRS) {
7749
        // Make sure that ETRF2000 to ETRF2014 doesn't go through ITRF9x or
7750
        // ITRF>2014
7751
0
        if (ETRFtoETRF && intermediateCRS &&
7752
0
            starts_with(intermediateCRS->nameStr(), "ITRF")) {
7753
0
            const auto normalizeDate = [](int v) {
7754
0
                return (v >= 80 && v <= 99) ? v + 1900 : v;
7755
0
            };
7756
0
            const int srcDate = normalizeDate(
7757
0
                atoi(sourceCRS->nameStr().c_str() + strlen("ETRF")));
7758
0
            const int tgtDate = normalizeDate(
7759
0
                atoi(targetCRS->nameStr().c_str() + strlen("ETRF")));
7760
0
            const int intermDate = normalizeDate(
7761
0
                atoi(intermediateCRS->nameStr().c_str() + strlen("ITRF")));
7762
0
            if (srcDate > 0 && tgtDate > 0 && intermDate > 0) {
7763
0
                if (intermDate < std::min(srcDate, tgtDate) ||
7764
0
                    intermDate > std::max(srcDate, tgtDate)) {
7765
0
                    return false;
7766
0
                }
7767
0
            }
7768
0
        }
7769
7770
        // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go through
7771
        // NAD83 generic. Cf https://github.com/OSGeo/PROJ/issues/4464
7772
0
        if (NAD83_CSRS_to_NAD83_CSRS && intermediateCRS &&
7773
0
            (intermediateCRS->nameStr() == "NAD83" ||
7774
0
             intermediateCRS->nameStr() == "WGS 84")) {
7775
0
            return false;
7776
0
        }
7777
7778
0
        return true;
7779
0
    };
7780
7781
0
    for (const auto &row : res) {
7782
0
        const auto &table1 = row[0];
7783
0
        const auto &auth_name1 = row[1];
7784
0
        const auto &code1 = row[2];
7785
        // const auto &accuracy1 = row[3];
7786
0
        const auto &table2 = row[4];
7787
0
        const auto &auth_name2 = row[5];
7788
0
        const auto &code2 = row[6];
7789
        // const auto &accuracy2 = row[7];
7790
0
        try {
7791
0
            auto op1 =
7792
0
                d->createFactory(auth_name1)
7793
0
                    ->createCoordinateOperation(
7794
0
                        code1, true, usePROJAlternativeGridNames, table1);
7795
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7796
0
                                   targetCRSAuthName, targetCRSCode)) {
7797
0
                continue;
7798
0
            }
7799
0
            if (!checkPivot(op1->targetCRS())) {
7800
0
                continue;
7801
0
            }
7802
0
            auto op2 =
7803
0
                d->createFactory(auth_name2)
7804
0
                    ->createCoordinateOperation(
7805
0
                        code2, true, usePROJAlternativeGridNames, table2);
7806
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7807
0
                                   targetCRSAuthName, targetCRSCode)) {
7808
0
                continue;
7809
0
            }
7810
7811
0
            listTmp.emplace_back(
7812
0
                operation::ConcatenatedOperation::createComputeMetadata(
7813
0
                    {std::move(op1), std::move(op2)}, false));
7814
0
        } catch (const std::exception &e) {
7815
            // Mostly for debugging purposes when using an inconsistent
7816
            // database
7817
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7818
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7819
0
            } else {
7820
0
                throw;
7821
0
            }
7822
0
        }
7823
0
    }
7824
7825
    // Case (source->intermediate) and (target->intermediate)
7826
0
    sql = sqlProlog +
7827
0
          "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
7828
0
          "AND v1.target_crs_code = v2.target_crs_code " +
7829
0
          joinArea +
7830
0
          "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
7831
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
7832
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7833
0
        sql += criterionOnIntermediateCRS;
7834
0
    }
7835
0
    intermediateWhere = buildIntermediateWhere("target", "target");
7836
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7837
0
    if (discardSuperseded) {
7838
0
        res = filterOutSuperseded(std::move(res));
7839
0
    }
7840
7841
0
    for (const auto &row : res) {
7842
0
        const auto &table1 = row[0];
7843
0
        const auto &auth_name1 = row[1];
7844
0
        const auto &code1 = row[2];
7845
        // const auto &accuracy1 = row[3];
7846
0
        const auto &table2 = row[4];
7847
0
        const auto &auth_name2 = row[5];
7848
0
        const auto &code2 = row[6];
7849
        // const auto &accuracy2 = row[7];
7850
0
        try {
7851
0
            auto op1 =
7852
0
                d->createFactory(auth_name1)
7853
0
                    ->createCoordinateOperation(
7854
0
                        code1, true, usePROJAlternativeGridNames, table1);
7855
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7856
0
                                   targetCRSAuthName, targetCRSCode)) {
7857
0
                continue;
7858
0
            }
7859
0
            if (!checkPivot(op1->targetCRS())) {
7860
0
                continue;
7861
0
            }
7862
0
            auto op2 =
7863
0
                d->createFactory(auth_name2)
7864
0
                    ->createCoordinateOperation(
7865
0
                        code2, true, usePROJAlternativeGridNames, table2);
7866
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7867
0
                                   targetCRSAuthName, targetCRSCode)) {
7868
0
                continue;
7869
0
            }
7870
7871
0
            listTmp.emplace_back(
7872
0
                operation::ConcatenatedOperation::createComputeMetadata(
7873
0
                    {std::move(op1), op2->inverse()}, false));
7874
0
        } catch (const std::exception &e) {
7875
            // Mostly for debugging purposes when using an inconsistent
7876
            // database
7877
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7878
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7879
0
            } else {
7880
0
                throw;
7881
0
            }
7882
0
        }
7883
0
    }
7884
7885
    // Case (intermediate->source) and (intermediate->target)
7886
0
    sql = sqlProlog +
7887
0
          "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
7888
0
          "AND v1.source_crs_code = v2.source_crs_code " +
7889
0
          joinArea +
7890
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
7891
0
          "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
7892
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7893
0
        if (!minDate.empty()) {
7894
0
            criterionOnIntermediateCRS =
7895
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x "
7896
0
                "JOIN geodetic_datum y "
7897
0
                "ON "
7898
0
                "y.auth_name = x.datum_auth_name AND "
7899
0
                "y.code = x.datum_code "
7900
0
                "WHERE "
7901
0
                "x.auth_name = v1.source_crs_auth_name AND "
7902
0
                "x.code = v1.source_crs_code AND "
7903
0
                "x.type IN ('geographic 2D', 'geographic 3D') AND "
7904
0
                "(y.publication_date IS NULL OR "
7905
0
                "(y.publication_date >= '" +
7906
0
                minDate + "'))) ";
7907
0
        } else {
7908
0
            criterionOnIntermediateCRS =
7909
0
                "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
7910
0
                "x.auth_name = v1.source_crs_auth_name AND "
7911
0
                "x.code = v1.source_crs_code AND "
7912
0
                "x.type IN ('geographic 2D', 'geographic 3D')) ";
7913
0
        }
7914
0
        sql += criterionOnIntermediateCRS;
7915
0
    }
7916
0
    intermediateWhere = buildIntermediateWhere("source", "source");
7917
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7918
0
    if (discardSuperseded) {
7919
0
        res = filterOutSuperseded(std::move(res));
7920
0
    }
7921
0
    for (const auto &row : res) {
7922
0
        const auto &table1 = row[0];
7923
0
        const auto &auth_name1 = row[1];
7924
0
        const auto &code1 = row[2];
7925
        // const auto &accuracy1 = row[3];
7926
0
        const auto &table2 = row[4];
7927
0
        const auto &auth_name2 = row[5];
7928
0
        const auto &code2 = row[6];
7929
        // const auto &accuracy2 = row[7];
7930
0
        try {
7931
0
            auto op1 =
7932
0
                d->createFactory(auth_name1)
7933
0
                    ->createCoordinateOperation(
7934
0
                        code1, true, usePROJAlternativeGridNames, table1);
7935
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7936
0
                                   targetCRSAuthName, targetCRSCode)) {
7937
0
                continue;
7938
0
            }
7939
0
            if (!checkPivot(op1->sourceCRS())) {
7940
0
                continue;
7941
0
            }
7942
0
            auto op2 =
7943
0
                d->createFactory(auth_name2)
7944
0
                    ->createCoordinateOperation(
7945
0
                        code2, true, usePROJAlternativeGridNames, table2);
7946
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
7947
0
                                   targetCRSAuthName, targetCRSCode)) {
7948
0
                continue;
7949
0
            }
7950
7951
0
            listTmp.emplace_back(
7952
0
                operation::ConcatenatedOperation::createComputeMetadata(
7953
0
                    {op1->inverse(), std::move(op2)}, false));
7954
0
        } catch (const std::exception &e) {
7955
            // Mostly for debugging purposes when using an inconsistent
7956
            // database
7957
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
7958
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
7959
0
            } else {
7960
0
                throw;
7961
0
            }
7962
0
        }
7963
0
    }
7964
7965
    // Case (intermediate->source) and (target->intermediate)
7966
0
    sql = sqlProlog +
7967
0
          "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
7968
0
          "AND v1.source_crs_code = v2.target_crs_code " +
7969
0
          joinArea +
7970
0
          "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
7971
0
          "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
7972
0
    if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
7973
0
        sql += criterionOnIntermediateCRS;
7974
0
    }
7975
0
    intermediateWhere = buildIntermediateWhere("source", "target");
7976
0
    res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
7977
0
    if (discardSuperseded) {
7978
0
        res = filterOutSuperseded(std::move(res));
7979
0
    }
7980
0
    for (const auto &row : res) {
7981
0
        const auto &table1 = row[0];
7982
0
        const auto &auth_name1 = row[1];
7983
0
        const auto &code1 = row[2];
7984
        // const auto &accuracy1 = row[3];
7985
0
        const auto &table2 = row[4];
7986
0
        const auto &auth_name2 = row[5];
7987
0
        const auto &code2 = row[6];
7988
        // const auto &accuracy2 = row[7];
7989
0
        try {
7990
0
            auto op1 =
7991
0
                d->createFactory(auth_name1)
7992
0
                    ->createCoordinateOperation(
7993
0
                        code1, true, usePROJAlternativeGridNames, table1);
7994
0
            if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
7995
0
                                   targetCRSAuthName, targetCRSCode)) {
7996
0
                continue;
7997
0
            }
7998
0
            if (!checkPivot(op1->sourceCRS())) {
7999
0
                continue;
8000
0
            }
8001
0
            auto op2 =
8002
0
                d->createFactory(auth_name2)
8003
0
                    ->createCoordinateOperation(
8004
0
                        code2, true, usePROJAlternativeGridNames, table2);
8005
0
            if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
8006
0
                                   targetCRSAuthName, targetCRSCode)) {
8007
0
                continue;
8008
0
            }
8009
8010
0
            listTmp.emplace_back(
8011
0
                operation::ConcatenatedOperation::createComputeMetadata(
8012
0
                    {op1->inverse(), op2->inverse()}, false));
8013
0
        } catch (const std::exception &e) {
8014
            // Mostly for debugging purposes when using an inconsistent
8015
            // database
8016
0
            if (getenv("PROJ_IGNORE_INSTANTIATION_ERRORS")) {
8017
0
                fprintf(stderr, "Ignoring invalid operation: %s\n", e.what());
8018
0
            } else {
8019
0
                throw;
8020
0
            }
8021
0
        }
8022
0
    }
8023
8024
0
    std::vector<operation::CoordinateOperationNNPtr> list;
8025
0
    for (const auto &op : listTmp) {
8026
0
        if (!discardIfMissingGrid ||
8027
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8028
0
            list.emplace_back(op);
8029
0
        }
8030
0
    }
8031
8032
0
    return list;
8033
0
}
8034
8035
// ---------------------------------------------------------------------------
8036
8037
//! @cond Doxygen_Suppress
8038
8039
struct TrfmInfo {
8040
    std::string situation{};
8041
    std::string table_name{};
8042
    std::string auth_name{};
8043
    std::string code{};
8044
    std::string name{};
8045
    double west = 0;
8046
    double south = 0;
8047
    double east = 0;
8048
    double north = 0;
8049
};
8050
8051
// ---------------------------------------------------------------------------
8052
8053
std::vector<operation::CoordinateOperationNNPtr>
8054
AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
8055
    const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
8056
    const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
8057
    const std::string &targetCRSAuthName, const std::string &targetCRSCode,
8058
    bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
8059
    bool considerKnownGridsAsAvailable, bool discardSuperseded,
8060
    const std::vector<std::string> &allowedAuthorities,
8061
    const metadata::ExtentPtr &intersectingExtent1,
8062
0
    const metadata::ExtentPtr &intersectingExtent2) const {
8063
8064
0
    std::vector<operation::CoordinateOperationNNPtr> listTmp;
8065
8066
0
    if (sourceCRSAuthName == targetCRSAuthName &&
8067
0
        sourceCRSCode == targetCRSCode) {
8068
0
        return listTmp;
8069
0
    }
8070
0
    const auto sourceGeodCRS =
8071
0
        dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get());
8072
0
    const auto targetGeodCRS =
8073
0
        dynamic_cast<crs::GeodeticCRS *>(targetCRS.get());
8074
0
    if (!sourceGeodCRS || !targetGeodCRS) {
8075
0
        return listTmp;
8076
0
    }
8077
8078
0
    const bool NAD83_CSRS_to_NAD83_CSRS =
8079
0
        starts_with(sourceGeodCRS->nameStr(), "NAD83(CSRS)") &&
8080
0
        starts_with(targetGeodCRS->nameStr(), "NAD83(CSRS)");
8081
8082
0
    const auto GetListCRSWithSameDatum = [this](const crs::GeodeticCRS *crs,
8083
0
                                                const std::string &crsAuthName,
8084
0
                                                const std::string &crsCode) {
8085
        // Find all geodetic CRS that share the same datum as the CRS
8086
0
        SQLResultSet listCRS;
8087
8088
0
        const common::IdentifiedObject *obj = crs->datum().get();
8089
0
        if (obj == nullptr)
8090
0
            obj = crs->datumEnsemble().get();
8091
0
        assert(obj != nullptr);
8092
0
        const auto &ids = obj->identifiers();
8093
0
        std::string datumAuthName;
8094
0
        std::string datumCode;
8095
0
        if (!ids.empty()) {
8096
0
            const auto &id = ids.front();
8097
0
            datumAuthName = *(id->codeSpace());
8098
0
            datumCode = id->code();
8099
0
        } else {
8100
0
            const auto res =
8101
0
                d->run("SELECT datum_auth_name, datum_code FROM "
8102
0
                       "geodetic_crs WHERE auth_name = ? AND code = ?",
8103
0
                       {crsAuthName, crsCode});
8104
0
            if (res.size() != 1) {
8105
0
                return listCRS;
8106
0
            }
8107
0
            const auto &row = res.front();
8108
0
            datumAuthName = row[0];
8109
0
            datumCode = row[1];
8110
0
        }
8111
8112
0
        listCRS =
8113
0
            d->run("SELECT auth_name, code FROM geodetic_crs WHERE "
8114
0
                   "datum_auth_name = ? AND datum_code = ? AND deprecated = 0",
8115
0
                   {datumAuthName, datumCode});
8116
0
        if (listCRS.empty()) {
8117
            // Can happen if the CRS is deprecated
8118
0
            listCRS.emplace_back(SQLRow{crsAuthName, crsCode});
8119
0
        }
8120
0
        return listCRS;
8121
0
    };
8122
8123
0
    const SQLResultSet listSourceCRS = GetListCRSWithSameDatum(
8124
0
        sourceGeodCRS, sourceCRSAuthName, sourceCRSCode);
8125
0
    const SQLResultSet listTargetCRS = GetListCRSWithSameDatum(
8126
0
        targetGeodCRS, targetCRSAuthName, targetCRSCode);
8127
0
    if (listSourceCRS.empty() || listTargetCRS.empty()) {
8128
        // would happen only if we had CRS objects in the database without a
8129
        // link to a datum.
8130
0
        return listTmp;
8131
0
    }
8132
8133
0
    ListOfParams params;
8134
0
    const auto BuildSQLPart = [this, NAD83_CSRS_to_NAD83_CSRS,
8135
0
                               &allowedAuthorities, &params, &listSourceCRS,
8136
0
                               &listTargetCRS](bool isSourceCRS,
8137
0
                                               bool selectOnTarget) {
8138
0
        std::string situation;
8139
0
        if (isSourceCRS)
8140
0
            situation = "src";
8141
0
        else
8142
0
            situation = "tgt";
8143
0
        if (selectOnTarget)
8144
0
            situation += "_is_tgt";
8145
0
        else
8146
0
            situation += "_is_src";
8147
0
        const std::string prefix1(selectOnTarget ? "source" : "target");
8148
0
        const std::string prefix2(selectOnTarget ? "target" : "source");
8149
0
        std::string sql("SELECT '");
8150
0
        sql += situation;
8151
0
        sql += "' as situation, v.table_name, v.auth_name, "
8152
0
               "v.code, v.name, gcrs.datum_auth_name, gcrs.datum_code, "
8153
0
               "a.west_lon, a.south_lat, a.east_lon, a.north_lat "
8154
0
               "FROM coordinate_operation_view v "
8155
0
               "JOIN geodetic_crs gcrs on gcrs.auth_name = ";
8156
0
        sql += prefix1;
8157
0
        sql += "_crs_auth_name AND gcrs.code = ";
8158
0
        sql += prefix1;
8159
0
        sql += "_crs_code "
8160
8161
0
               "LEFT JOIN usage u ON "
8162
0
               "u.object_table_name = v.table_name AND "
8163
0
               "u.object_auth_name = v.auth_name AND "
8164
0
               "u.object_code = v.code "
8165
0
               "LEFT JOIN extent a "
8166
0
               "ON a.auth_name = u.extent_auth_name AND "
8167
0
               "a.code = u.extent_code "
8168
0
               "WHERE v.deprecated = 0 AND (";
8169
8170
0
        std::string cond;
8171
8172
0
        const auto &list = isSourceCRS ? listSourceCRS : listTargetCRS;
8173
0
        for (const auto &row : list) {
8174
0
            if (!cond.empty())
8175
0
                cond += " OR ";
8176
0
            cond += '(';
8177
0
            cond += prefix2;
8178
0
            cond += "_crs_auth_name = ? AND ";
8179
0
            cond += prefix2;
8180
0
            cond += "_crs_code = ?)";
8181
0
            params.emplace_back(row[0]);
8182
0
            params.emplace_back(row[1]);
8183
0
        }
8184
8185
0
        sql += cond;
8186
0
        sql += ") ";
8187
8188
0
        if (!allowedAuthorities.empty()) {
8189
0
            sql += "AND v.auth_name IN (";
8190
0
            for (size_t i = 0; i < allowedAuthorities.size(); i++) {
8191
0
                if (i > 0)
8192
0
                    sql += ',';
8193
0
                sql += '?';
8194
0
            }
8195
0
            sql += ") ";
8196
0
            for (const auto &allowedAuthority : allowedAuthorities) {
8197
0
                params.emplace_back(allowedAuthority);
8198
0
            }
8199
0
        }
8200
0
        if (d->hasAuthorityRestriction()) {
8201
0
            sql += "AND v.auth_name = ? ";
8202
0
            params.emplace_back(d->authority());
8203
0
        }
8204
0
        if (NAD83_CSRS_to_NAD83_CSRS) {
8205
            // Make sure that NAD83(CSRS)[x] to NAD83(CSRS)[y) doesn't go
8206
            // through NAD83 generic. Cf
8207
            // https://github.com/OSGeo/PROJ/issues/4464
8208
0
            sql += "AND gcrs.name NOT IN ('NAD83', 'WGS 84') ";
8209
0
        }
8210
8211
0
        return sql;
8212
0
    };
8213
8214
0
    std::string sql(BuildSQLPart(true, true));
8215
0
    sql += "UNION ALL ";
8216
0
    sql += BuildSQLPart(false, true);
8217
0
    sql += "UNION ALL ";
8218
0
    sql += BuildSQLPart(true, false);
8219
0
    sql += "UNION ALL ";
8220
0
    sql += BuildSQLPart(false, false);
8221
    // fprintf(stderr, "sql : %s\n", sql.c_str());
8222
8223
    // Find all operations that have as source/target CRS a CRS that
8224
    // share the same datum as the source or targetCRS
8225
0
    const auto res = d->run(sql, params);
8226
8227
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfSource;
8228
0
    std::map<std::string, std::list<TrfmInfo>> mapIntermDatumOfTarget;
8229
8230
0
    for (const auto &row : res) {
8231
0
        try {
8232
0
            TrfmInfo trfm;
8233
0
            trfm.situation = row[0];
8234
0
            trfm.table_name = row[1];
8235
0
            trfm.auth_name = row[2];
8236
0
            trfm.code = row[3];
8237
0
            trfm.name = row[4];
8238
0
            const auto &datum_auth_name = row[5];
8239
0
            const auto &datum_code = row[6];
8240
0
            trfm.west = c_locale_stod(row[7]);
8241
0
            trfm.south = c_locale_stod(row[8]);
8242
0
            trfm.east = c_locale_stod(row[9]);
8243
0
            trfm.north = c_locale_stod(row[10]);
8244
0
            const std::string key =
8245
0
                std::string(datum_auth_name).append(":").append(datum_code);
8246
0
            if (trfm.situation == "src_is_tgt" ||
8247
0
                trfm.situation == "src_is_src")
8248
0
                mapIntermDatumOfSource[key].emplace_back(std::move(trfm));
8249
0
            else
8250
0
                mapIntermDatumOfTarget[key].emplace_back(std::move(trfm));
8251
0
        } catch (const std::exception &) {
8252
0
        }
8253
0
    }
8254
8255
0
    std::vector<const metadata::GeographicBoundingBox *> extraBbox;
8256
0
    for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
8257
0
        if (extent) {
8258
0
            const auto &geogExtent = extent->geographicElements();
8259
0
            if (geogExtent.size() == 1) {
8260
0
                auto bbox =
8261
0
                    dynamic_cast<const metadata::GeographicBoundingBox *>(
8262
0
                        geogExtent[0].get());
8263
0
                if (bbox) {
8264
0
                    const double south_lat = bbox->southBoundLatitude();
8265
0
                    const double west_lon = bbox->westBoundLongitude();
8266
0
                    const double north_lat = bbox->northBoundLatitude();
8267
0
                    const double east_lon = bbox->eastBoundLongitude();
8268
0
                    if (south_lat != -90.0 || west_lon != -180.0 ||
8269
0
                        north_lat != 90.0 || east_lon != 180.0) {
8270
0
                        extraBbox.emplace_back(bbox);
8271
0
                    }
8272
0
                }
8273
0
            }
8274
0
        }
8275
0
    }
8276
8277
0
    std::map<std::string, operation::CoordinateOperationPtr> oMapTrfmKeyToOp;
8278
0
    std::list<std::pair<TrfmInfo, TrfmInfo>> candidates;
8279
0
    std::map<std::string, TrfmInfo> setOfTransformations;
8280
8281
0
    const auto MakeKey = [](const TrfmInfo &trfm) {
8282
0
        return trfm.table_name + '_' + trfm.auth_name + '_' + trfm.code;
8283
0
    };
8284
8285
    // Find transformations that share a pivot datum, and do bbox filtering
8286
0
    for (const auto &kvSource : mapIntermDatumOfSource) {
8287
0
        const auto &listTrmfSource = kvSource.second;
8288
0
        auto iter = mapIntermDatumOfTarget.find(kvSource.first);
8289
0
        if (iter == mapIntermDatumOfTarget.end())
8290
0
            continue;
8291
8292
0
        const auto &listTrfmTarget = iter->second;
8293
0
        for (const auto &trfmSource : listTrmfSource) {
8294
0
            auto bbox1 = metadata::GeographicBoundingBox::create(
8295
0
                trfmSource.west, trfmSource.south, trfmSource.east,
8296
0
                trfmSource.north);
8297
0
            bool okBbox1 = true;
8298
0
            for (const auto bbox : extraBbox)
8299
0
                okBbox1 &= bbox->intersects(bbox1);
8300
0
            if (!okBbox1)
8301
0
                continue;
8302
8303
0
            const std::string key1 = MakeKey(trfmSource);
8304
8305
0
            for (const auto &trfmTarget : listTrfmTarget) {
8306
0
                auto bbox2 = metadata::GeographicBoundingBox::create(
8307
0
                    trfmTarget.west, trfmTarget.south, trfmTarget.east,
8308
0
                    trfmTarget.north);
8309
0
                if (!bbox1->intersects(bbox2))
8310
0
                    continue;
8311
0
                bool okBbox2 = true;
8312
0
                for (const auto bbox : extraBbox)
8313
0
                    okBbox2 &= bbox->intersects(bbox2);
8314
0
                if (!okBbox2)
8315
0
                    continue;
8316
8317
0
                operation::CoordinateOperationPtr op1;
8318
0
                if (oMapTrfmKeyToOp.find(key1) == oMapTrfmKeyToOp.end()) {
8319
0
                    auto op1NN = d->createFactory(trfmSource.auth_name)
8320
0
                                     ->createCoordinateOperation(
8321
0
                                         trfmSource.code, true,
8322
0
                                         usePROJAlternativeGridNames,
8323
0
                                         trfmSource.table_name);
8324
0
                    op1 = op1NN.as_nullable();
8325
0
                    if (useIrrelevantPivot(op1NN, sourceCRSAuthName,
8326
0
                                           sourceCRSCode, targetCRSAuthName,
8327
0
                                           targetCRSCode)) {
8328
0
                        op1.reset();
8329
0
                    }
8330
0
                    oMapTrfmKeyToOp[key1] = op1;
8331
0
                } else {
8332
0
                    op1 = oMapTrfmKeyToOp[key1];
8333
0
                }
8334
0
                if (op1 == nullptr)
8335
0
                    continue;
8336
8337
0
                const std::string key2 = MakeKey(trfmTarget);
8338
8339
0
                operation::CoordinateOperationPtr op2;
8340
0
                if (oMapTrfmKeyToOp.find(key2) == oMapTrfmKeyToOp.end()) {
8341
0
                    auto op2NN = d->createFactory(trfmTarget.auth_name)
8342
0
                                     ->createCoordinateOperation(
8343
0
                                         trfmTarget.code, true,
8344
0
                                         usePROJAlternativeGridNames,
8345
0
                                         trfmTarget.table_name);
8346
0
                    op2 = op2NN.as_nullable();
8347
0
                    if (useIrrelevantPivot(op2NN, sourceCRSAuthName,
8348
0
                                           sourceCRSCode, targetCRSAuthName,
8349
0
                                           targetCRSCode)) {
8350
0
                        op2.reset();
8351
0
                    }
8352
0
                    oMapTrfmKeyToOp[key2] = op2;
8353
0
                } else {
8354
0
                    op2 = oMapTrfmKeyToOp[key2];
8355
0
                }
8356
0
                if (op2 == nullptr)
8357
0
                    continue;
8358
8359
0
                candidates.emplace_back(
8360
0
                    std::pair<TrfmInfo, TrfmInfo>(trfmSource, trfmTarget));
8361
0
                setOfTransformations[key1] = trfmSource;
8362
0
                setOfTransformations[key2] = trfmTarget;
8363
0
            }
8364
0
        }
8365
0
    }
8366
8367
0
    std::set<std::string> setSuperseded;
8368
0
    if (discardSuperseded && !setOfTransformations.empty()) {
8369
0
        std::string findSupersededSql(
8370
0
            "SELECT superseded_table_name, "
8371
0
            "superseded_auth_name, superseded_code, "
8372
0
            "replacement_auth_name, replacement_code "
8373
0
            "FROM supersession WHERE same_source_target_crs = 1 AND (");
8374
0
        bool findSupersededFirstWhere = true;
8375
0
        ListOfParams findSupersededParams;
8376
8377
0
        const auto keyMapSupersession = [](const std::string &table_name,
8378
0
                                           const std::string &auth_name,
8379
0
                                           const std::string &code) {
8380
0
            return table_name + auth_name + code;
8381
0
        };
8382
8383
0
        std::set<std::pair<std::string, std::string>> setTransf;
8384
0
        for (const auto &kv : setOfTransformations) {
8385
0
            const auto &table = kv.second.table_name;
8386
0
            const auto &auth_name = kv.second.auth_name;
8387
0
            const auto &code = kv.second.code;
8388
8389
0
            if (!findSupersededFirstWhere)
8390
0
                findSupersededSql += " OR ";
8391
0
            findSupersededFirstWhere = false;
8392
0
            findSupersededSql +=
8393
0
                "(superseded_table_name = ? AND replacement_table_name = "
8394
0
                "superseded_table_name AND superseded_auth_name = ? AND "
8395
0
                "superseded_code = ?)";
8396
0
            findSupersededParams.push_back(table);
8397
0
            findSupersededParams.push_back(auth_name);
8398
0
            findSupersededParams.push_back(code);
8399
8400
0
            setTransf.insert(
8401
0
                std::pair<std::string, std::string>(auth_name, code));
8402
0
        }
8403
0
        findSupersededSql += ')';
8404
8405
0
        std::map<std::string, std::vector<std::pair<std::string, std::string>>>
8406
0
            mapSupersession;
8407
8408
0
        const auto resSuperseded =
8409
0
            d->run(findSupersededSql, findSupersededParams);
8410
0
        for (const auto &row : resSuperseded) {
8411
0
            const auto &superseded_table_name = row[0];
8412
0
            const auto &superseded_auth_name = row[1];
8413
0
            const auto &superseded_code = row[2];
8414
0
            const auto &replacement_auth_name = row[3];
8415
0
            const auto &replacement_code = row[4];
8416
0
            mapSupersession[keyMapSupersession(superseded_table_name,
8417
0
                                               superseded_auth_name,
8418
0
                                               superseded_code)]
8419
0
                .push_back(std::pair<std::string, std::string>(
8420
0
                    replacement_auth_name, replacement_code));
8421
0
        }
8422
8423
0
        for (const auto &kv : setOfTransformations) {
8424
0
            const auto &table = kv.second.table_name;
8425
0
            const auto &auth_name = kv.second.auth_name;
8426
0
            const auto &code = kv.second.code;
8427
8428
0
            const auto iter = mapSupersession.find(
8429
0
                keyMapSupersession(table, auth_name, code));
8430
0
            if (iter != mapSupersession.end()) {
8431
0
                bool foundReplacement = false;
8432
0
                for (const auto &replacement : iter->second) {
8433
0
                    const auto &replacement_auth_name = replacement.first;
8434
0
                    const auto &replacement_code = replacement.second;
8435
0
                    if (setTransf.find(std::pair<std::string, std::string>(
8436
0
                            replacement_auth_name, replacement_code)) !=
8437
0
                        setTransf.end()) {
8438
                        // Skip transformations that are superseded by others
8439
                        // that got
8440
                        // returned in the result set.
8441
0
                        foundReplacement = true;
8442
0
                        break;
8443
0
                    }
8444
0
                }
8445
0
                if (foundReplacement) {
8446
0
                    setSuperseded.insert(kv.first);
8447
0
                }
8448
0
            }
8449
0
        }
8450
0
    }
8451
8452
0
    std::string sourceDatumPubDate;
8453
0
    const auto sourceDatum = sourceGeodCRS->datumNonNull(d->context());
8454
0
    if (sourceDatum->publicationDate().has_value()) {
8455
0
        sourceDatumPubDate = sourceDatum->publicationDate()->toString();
8456
0
    }
8457
8458
0
    std::string targetDatumPubDate;
8459
0
    const auto targetDatum = targetGeodCRS->datumNonNull(d->context());
8460
0
    if (targetDatum->publicationDate().has_value()) {
8461
0
        targetDatumPubDate = targetDatum->publicationDate()->toString();
8462
0
    }
8463
8464
0
    const std::string mostAncientDatumPubDate =
8465
0
        (!targetDatumPubDate.empty() &&
8466
0
         (sourceDatumPubDate.empty() ||
8467
0
          targetDatumPubDate < sourceDatumPubDate))
8468
0
            ? targetDatumPubDate
8469
0
            : sourceDatumPubDate;
8470
8471
0
    auto opFactory = operation::CoordinateOperationFactory::create();
8472
0
    for (const auto &pair : candidates) {
8473
0
        const auto &trfmSource = pair.first;
8474
0
        const auto &trfmTarget = pair.second;
8475
0
        const std::string key1 = MakeKey(trfmSource);
8476
0
        const std::string key2 = MakeKey(trfmTarget);
8477
0
        if (setSuperseded.find(key1) != setSuperseded.end() ||
8478
0
            setSuperseded.find(key2) != setSuperseded.end()) {
8479
0
            continue;
8480
0
        }
8481
0
        auto op1 = oMapTrfmKeyToOp[key1];
8482
0
        auto op2 = oMapTrfmKeyToOp[key2];
8483
0
        auto op1NN = NN_NO_CHECK(op1);
8484
0
        auto op2NN = NN_NO_CHECK(op2);
8485
0
        if (trfmSource.situation == "src_is_tgt")
8486
0
            op1NN = op1NN->inverse();
8487
0
        if (trfmTarget.situation == "tgt_is_src")
8488
0
            op2NN = op2NN->inverse();
8489
8490
0
        const auto &op1Source = op1NN->sourceCRS();
8491
0
        const auto &op1Target = op1NN->targetCRS();
8492
0
        const auto &op2Source = op2NN->sourceCRS();
8493
0
        const auto &op2Target = op2NN->targetCRS();
8494
0
        if (!(op1Source && op1Target && op2Source && op2Target)) {
8495
0
            continue;
8496
0
        }
8497
8498
        // Skip operations using a datum that is older than the source or
8499
        // target datum (e.g to avoid ED50 to WGS84 to go through NTF)
8500
0
        if (!mostAncientDatumPubDate.empty()) {
8501
0
            const auto isOlderCRS = [this, &mostAncientDatumPubDate](
8502
0
                                        const crs::CRSPtr &crs) {
8503
0
                const auto geogCRS =
8504
0
                    dynamic_cast<const crs::GeodeticCRS *>(crs.get());
8505
0
                if (geogCRS) {
8506
0
                    const auto datum = geogCRS->datumNonNull(d->context());
8507
                    // Hum, theoretically we'd want to check
8508
                    // datum->publicationDate()->toString() <
8509
                    // mostAncientDatumPubDate but that would exclude doing
8510
                    // IG05/12 Intermediate CRS to ITRF2014 through ITRF2008,
8511
                    // since IG05/12 Intermediate CRS has been published later
8512
                    // than ITRF2008. So use a cut of date for ancient vs
8513
                    // "modern" era.
8514
0
                    constexpr const char *CUT_OFF_DATE = "1900-01-01";
8515
0
                    if (datum->publicationDate().has_value() &&
8516
0
                        datum->publicationDate()->toString() < CUT_OFF_DATE &&
8517
0
                        mostAncientDatumPubDate > CUT_OFF_DATE) {
8518
0
                        return true;
8519
0
                    }
8520
0
                }
8521
0
                return false;
8522
0
            };
8523
8524
0
            if (isOlderCRS(op1Source) || isOlderCRS(op1Target) ||
8525
0
                isOlderCRS(op2Source) || isOlderCRS(op2Target))
8526
0
                continue;
8527
0
        }
8528
8529
0
        std::vector<operation::CoordinateOperationNNPtr> steps;
8530
8531
0
        if (!sourceCRS->isEquivalentTo(
8532
0
                op1Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8533
0
            auto opFirst =
8534
0
                opFactory->createOperation(sourceCRS, NN_NO_CHECK(op1Source));
8535
0
            assert(opFirst);
8536
0
            steps.emplace_back(NN_NO_CHECK(opFirst));
8537
0
        }
8538
8539
0
        steps.emplace_back(op1NN);
8540
8541
0
        if (!op1Target->isEquivalentTo(
8542
0
                op2Source.get(), util::IComparable::Criterion::EQUIVALENT)) {
8543
0
            auto opMiddle = opFactory->createOperation(NN_NO_CHECK(op1Target),
8544
0
                                                       NN_NO_CHECK(op2Source));
8545
0
            assert(opMiddle);
8546
0
            steps.emplace_back(NN_NO_CHECK(opMiddle));
8547
0
        }
8548
8549
0
        steps.emplace_back(op2NN);
8550
8551
0
        if (!op2Target->isEquivalentTo(
8552
0
                targetCRS.get(), util::IComparable::Criterion::EQUIVALENT)) {
8553
0
            auto opLast =
8554
0
                opFactory->createOperation(NN_NO_CHECK(op2Target), targetCRS);
8555
0
            assert(opLast);
8556
0
            steps.emplace_back(NN_NO_CHECK(opLast));
8557
0
        }
8558
8559
0
        listTmp.emplace_back(
8560
0
            operation::ConcatenatedOperation::createComputeMetadata(steps,
8561
0
                                                                    false));
8562
0
    }
8563
8564
0
    std::vector<operation::CoordinateOperationNNPtr> list;
8565
0
    for (const auto &op : listTmp) {
8566
0
        if (!discardIfMissingGrid ||
8567
0
            !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
8568
0
            list.emplace_back(op);
8569
0
        }
8570
0
    }
8571
8572
0
    return list;
8573
0
}
8574
8575
//! @endcond
8576
8577
// ---------------------------------------------------------------------------
8578
8579
/** \brief Returns the authority name associated to this factory.
8580
 * @return name.
8581
 */
8582
0
const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
8583
0
    return d->authority();
8584
0
}
8585
8586
// ---------------------------------------------------------------------------
8587
8588
/** \brief Returns the set of authority codes of the given object type.
8589
 *
8590
 * @param type Object type.
8591
 * @param allowDeprecated whether we should return deprecated objects as well.
8592
 * @return the set of authority codes for spatial reference objects of the given
8593
 * type
8594
 * @throw FactoryException in case of error.
8595
 */
8596
std::set<std::string>
8597
AuthorityFactory::getAuthorityCodes(const ObjectType &type,
8598
0
                                    bool allowDeprecated) const {
8599
0
    std::string sql;
8600
0
    switch (type) {
8601
0
    case ObjectType::PRIME_MERIDIAN:
8602
0
        sql = "SELECT code FROM prime_meridian WHERE ";
8603
0
        break;
8604
0
    case ObjectType::ELLIPSOID:
8605
0
        sql = "SELECT code FROM ellipsoid WHERE ";
8606
0
        break;
8607
0
    case ObjectType::DATUM:
8608
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8609
0
              "('geodetic_datum', 'vertical_datum', 'engineering_datum') AND ";
8610
0
        break;
8611
0
    case ObjectType::GEODETIC_REFERENCE_FRAME:
8612
0
        sql = "SELECT code FROM geodetic_datum WHERE ";
8613
0
        break;
8614
0
    case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
8615
0
        sql = "SELECT code FROM geodetic_datum WHERE "
8616
0
              "frame_reference_epoch IS NOT NULL AND ";
8617
0
        break;
8618
0
    case ObjectType::VERTICAL_REFERENCE_FRAME:
8619
0
        sql = "SELECT code FROM vertical_datum WHERE ";
8620
0
        break;
8621
0
    case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
8622
0
        sql = "SELECT code FROM vertical_datum WHERE "
8623
0
              "frame_reference_epoch IS NOT NULL AND ";
8624
0
        break;
8625
0
    case ObjectType::ENGINEERING_DATUM:
8626
0
        sql = "SELECT code FROM engineering_datum WHERE ";
8627
0
        break;
8628
0
    case ObjectType::CRS:
8629
0
        sql = "SELECT code FROM crs_view WHERE ";
8630
0
        break;
8631
0
    case ObjectType::GEODETIC_CRS:
8632
0
        sql = "SELECT code FROM geodetic_crs WHERE ";
8633
0
        break;
8634
0
    case ObjectType::GEOCENTRIC_CRS:
8635
0
        sql = "SELECT code FROM geodetic_crs WHERE type "
8636
0
              "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
8637
0
        break;
8638
0
    case ObjectType::GEOGRAPHIC_CRS:
8639
0
        sql = "SELECT code FROM geodetic_crs WHERE type IN "
8640
0
              "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
8641
0
        break;
8642
0
    case ObjectType::GEOGRAPHIC_2D_CRS:
8643
0
        sql =
8644
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
8645
0
            " AND ";
8646
0
        break;
8647
0
    case ObjectType::GEOGRAPHIC_3D_CRS:
8648
0
        sql =
8649
0
            "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
8650
0
            " AND ";
8651
0
        break;
8652
0
    case ObjectType::VERTICAL_CRS:
8653
0
        sql = "SELECT code FROM vertical_crs WHERE ";
8654
0
        break;
8655
0
    case ObjectType::PROJECTED_CRS:
8656
0
        sql = "SELECT code FROM projected_crs WHERE ";
8657
0
        break;
8658
0
    case ObjectType::COMPOUND_CRS:
8659
0
        sql = "SELECT code FROM compound_crs WHERE ";
8660
0
        break;
8661
0
    case ObjectType::ENGINEERING_CRS:
8662
0
        sql = "SELECT code FROM engineering_crs WHERE ";
8663
0
        break;
8664
0
    case ObjectType::COORDINATE_OPERATION:
8665
0
        sql =
8666
0
            "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
8667
0
        break;
8668
0
    case ObjectType::CONVERSION:
8669
0
        sql = "SELECT code FROM conversion WHERE ";
8670
0
        break;
8671
0
    case ObjectType::TRANSFORMATION:
8672
0
        sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
8673
0
              "'concatenated_operation' AND ";
8674
0
        break;
8675
0
    case ObjectType::CONCATENATED_OPERATION:
8676
0
        sql = "SELECT code FROM concatenated_operation WHERE ";
8677
0
        break;
8678
0
    case ObjectType::DATUM_ENSEMBLE:
8679
0
        sql = "SELECT code FROM object_view WHERE table_name IN "
8680
0
              "('geodetic_datum', 'vertical_datum') AND "
8681
0
              "type = 'ensemble' AND ";
8682
0
        break;
8683
0
    }
8684
8685
0
    sql += "auth_name = ?";
8686
0
    if (!allowDeprecated) {
8687
0
        sql += " AND deprecated = 0";
8688
0
    }
8689
8690
0
    auto res = d->run(sql, {d->authority()});
8691
0
    std::set<std::string> set;
8692
0
    for (const auto &row : res) {
8693
0
        set.insert(row[0]);
8694
0
    }
8695
0
    return set;
8696
0
}
8697
8698
// ---------------------------------------------------------------------------
8699
8700
/** \brief Gets a description of the object corresponding to a code.
8701
 *
8702
 * \note In case of several objects of different types with the same code,
8703
 * one of them will be arbitrarily selected. But if a CRS object is found, it
8704
 * will be selected.
8705
 *
8706
 * @param code Object code allocated by authority. (e.g. "4326")
8707
 * @return description.
8708
 * @throw NoSuchAuthorityCodeException if there is no matching object.
8709
 * @throw FactoryException in case of other errors.
8710
 */
8711
std::string
8712
0
AuthorityFactory::getDescriptionText(const std::string &code) const {
8713
0
    auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
8714
0
               "AND code = ? ORDER BY table_name";
8715
0
    auto sqlRes = d->runWithCodeParam(sql, code);
8716
0
    if (sqlRes.empty()) {
8717
0
        throw NoSuchAuthorityCodeException("object not found", d->authority(),
8718
0
                                           code);
8719
0
    }
8720
0
    std::string text;
8721
0
    for (const auto &row : sqlRes) {
8722
0
        const auto &tableName = row[1];
8723
0
        if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
8724
0
            tableName == "vertical_crs" || tableName == "compound_crs" ||
8725
0
            tableName == "engineering_crs") {
8726
0
            return row[0];
8727
0
        } else if (text.empty()) {
8728
0
            text = row[0];
8729
0
        }
8730
0
    }
8731
0
    return text;
8732
0
}
8733
8734
// ---------------------------------------------------------------------------
8735
8736
/** \brief Return a list of information on CRS objects
8737
 *
8738
 * This is functionally equivalent of listing the codes from an authority,
8739
 * instantiating
8740
 * a CRS object for each of them and getting the information from this CRS
8741
 * object, but this implementation has much less overhead.
8742
 *
8743
 * @throw FactoryException in case of error.
8744
 */
8745
0
std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
8746
8747
0
    const auto getSqlArea = [](const char *table_name) {
8748
0
        std::string sql("LEFT JOIN usage u ON u.object_table_name = '");
8749
0
        sql += table_name;
8750
0
        sql += "' AND "
8751
0
               "u.object_auth_name = c.auth_name AND "
8752
0
               "u.object_code = c.code "
8753
0
               "LEFT JOIN extent a "
8754
0
               "ON a.auth_name = u.extent_auth_name AND "
8755
0
               "a.code = u.extent_code ";
8756
0
        return sql;
8757
0
    };
8758
8759
0
    const auto getJoinCelestialBody = [](const char *crs_alias) {
8760
0
        std::string sql("LEFT JOIN geodetic_datum gd ON gd.auth_name = ");
8761
0
        sql += crs_alias;
8762
0
        sql += ".datum_auth_name AND gd.code = ";
8763
0
        sql += crs_alias;
8764
0
        sql += ".datum_code "
8765
0
               "LEFT JOIN ellipsoid e ON e.auth_name = gd.ellipsoid_auth_name "
8766
0
               "AND e.code = gd.ellipsoid_code "
8767
0
               "LEFT JOIN celestial_body cb ON "
8768
0
               "cb.auth_name = e.celestial_body_auth_name "
8769
0
               "AND cb.code = e.celestial_body_code ";
8770
0
        return sql;
8771
0
    };
8772
8773
0
    std::string sql = "SELECT * FROM ("
8774
0
                      "SELECT c.auth_name, c.code, c.name, c.type, "
8775
0
                      "c.deprecated, "
8776
0
                      "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8777
0
                      "a.description, NULL, cb.name FROM geodetic_crs c ";
8778
0
    sql += getSqlArea("geodetic_crs");
8779
0
    sql += getJoinCelestialBody("c");
8780
0
    ListOfParams params;
8781
0
    if (d->hasAuthorityRestriction()) {
8782
0
        sql += "WHERE c.auth_name = ? ";
8783
0
        params.emplace_back(d->authority());
8784
0
    }
8785
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'projected', "
8786
0
           "c.deprecated, "
8787
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8788
0
           "a.description, cm.name, cb.name AS conversion_method_name FROM "
8789
0
           "projected_crs c "
8790
0
           "LEFT JOIN conversion_table conv ON "
8791
0
           "c.conversion_auth_name = conv.auth_name AND "
8792
0
           "c.conversion_code = conv.code "
8793
0
           "LEFT JOIN conversion_method cm ON "
8794
0
           "conv.method_auth_name = cm.auth_name AND "
8795
0
           "conv.method_code = cm.code "
8796
0
           "LEFT JOIN geodetic_crs gcrs ON "
8797
0
           "gcrs.auth_name = c.geodetic_crs_auth_name "
8798
0
           "AND gcrs.code = c.geodetic_crs_code ";
8799
0
    sql += getSqlArea("projected_crs");
8800
0
    sql += getJoinCelestialBody("gcrs");
8801
0
    if (d->hasAuthorityRestriction()) {
8802
0
        sql += "WHERE c.auth_name = ? ";
8803
0
        params.emplace_back(d->authority());
8804
0
    }
8805
    // FIXME: we can't handle non-EARTH vertical CRS for now
8806
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'vertical', "
8807
0
           "c.deprecated, "
8808
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8809
0
           "a.description, NULL, 'Earth' FROM vertical_crs c ";
8810
0
    sql += getSqlArea("vertical_crs");
8811
0
    if (d->hasAuthorityRestriction()) {
8812
0
        sql += "WHERE c.auth_name = ? ";
8813
0
        params.emplace_back(d->authority());
8814
0
    }
8815
    // FIXME: we can't handle non-EARTH compound CRS for now
8816
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'compound', "
8817
0
           "c.deprecated, "
8818
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8819
0
           "a.description, NULL, 'Earth' FROM compound_crs c ";
8820
0
    sql += getSqlArea("compound_crs");
8821
0
    if (d->hasAuthorityRestriction()) {
8822
0
        sql += "WHERE c.auth_name = ? ";
8823
0
        params.emplace_back(d->authority());
8824
0
    }
8825
    // FIXME: we can't handle non-EARTH compound CRS for now
8826
0
    sql += "UNION ALL SELECT c.auth_name, c.code, c.name, 'engineering', "
8827
0
           "c.deprecated, "
8828
0
           "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
8829
0
           "a.description, NULL, 'Earth' FROM engineering_crs c ";
8830
0
    sql += getSqlArea("engineering_crs");
8831
0
    if (d->hasAuthorityRestriction()) {
8832
0
        sql += "WHERE c.auth_name = ? ";
8833
0
        params.emplace_back(d->authority());
8834
0
    }
8835
0
    sql += ") r ORDER BY auth_name, code";
8836
0
    auto sqlRes = d->run(sql, params);
8837
0
    std::list<AuthorityFactory::CRSInfo> res;
8838
0
    for (const auto &row : sqlRes) {
8839
0
        AuthorityFactory::CRSInfo info;
8840
0
        info.authName = row[0];
8841
0
        info.code = row[1];
8842
0
        info.name = row[2];
8843
0
        const auto &type = row[3];
8844
0
        if (type == GEOG_2D) {
8845
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
8846
0
        } else if (type == GEOG_3D) {
8847
0
            info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
8848
0
        } else if (type == GEOCENTRIC) {
8849
0
            info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
8850
0
        } else if (type == OTHER) {
8851
0
            info.type = AuthorityFactory::ObjectType::GEODETIC_CRS;
8852
0
        } else if (type == PROJECTED) {
8853
0
            info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
8854
0
        } else if (type == VERTICAL) {
8855
0
            info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
8856
0
        } else if (type == COMPOUND) {
8857
0
            info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
8858
0
        } else if (type == ENGINEERING) {
8859
0
            info.type = AuthorityFactory::ObjectType::ENGINEERING_CRS;
8860
0
        }
8861
0
        info.deprecated = row[4] == "1";
8862
0
        if (row[5].empty()) {
8863
0
            info.bbox_valid = false;
8864
0
        } else {
8865
0
            info.bbox_valid = true;
8866
0
            info.west_lon_degree = c_locale_stod(row[5]);
8867
0
            info.south_lat_degree = c_locale_stod(row[6]);
8868
0
            info.east_lon_degree = c_locale_stod(row[7]);
8869
0
            info.north_lat_degree = c_locale_stod(row[8]);
8870
0
        }
8871
0
        info.areaName = row[9];
8872
0
        info.projectionMethodName = row[10];
8873
0
        info.celestialBodyName = row[11];
8874
0
        res.emplace_back(info);
8875
0
    }
8876
0
    return res;
8877
0
}
8878
8879
// ---------------------------------------------------------------------------
8880
8881
//! @cond Doxygen_Suppress
8882
AuthorityFactory::UnitInfo::UnitInfo()
8883
0
    : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
8884
0
      deprecated{} {}
8885
//! @endcond
8886
8887
// ---------------------------------------------------------------------------
8888
8889
//! @cond Doxygen_Suppress
8890
0
AuthorityFactory::CelestialBodyInfo::CelestialBodyInfo() : authName{}, name{} {}
8891
//! @endcond
8892
8893
// ---------------------------------------------------------------------------
8894
8895
/** \brief Return the list of units.
8896
 * @throw FactoryException in case of error.
8897
 *
8898
 * @since 7.1
8899
 */
8900
0
std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
8901
0
    std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
8902
0
                      "proj_short_name, deprecated FROM unit_of_measure";
8903
0
    ListOfParams params;
8904
0
    if (d->hasAuthorityRestriction()) {
8905
0
        sql += " WHERE auth_name = ?";
8906
0
        params.emplace_back(d->authority());
8907
0
    }
8908
0
    sql += " ORDER BY auth_name, code";
8909
8910
0
    auto sqlRes = d->run(sql, params);
8911
0
    std::list<AuthorityFactory::UnitInfo> res;
8912
0
    for (const auto &row : sqlRes) {
8913
0
        AuthorityFactory::UnitInfo info;
8914
0
        info.authName = row[0];
8915
0
        info.code = row[1];
8916
0
        info.name = row[2];
8917
0
        const std::string &raw_category(row[3]);
8918
0
        if (raw_category == "length") {
8919
0
            info.category = info.name.find(" per ") != std::string::npos
8920
0
                                ? "linear_per_time"
8921
0
                                : "linear";
8922
0
        } else if (raw_category == "angle") {
8923
0
            info.category = info.name.find(" per ") != std::string::npos
8924
0
                                ? "angular_per_time"
8925
0
                                : "angular";
8926
0
        } else if (raw_category == "scale") {
8927
0
            info.category =
8928
0
                info.name.find(" per year") != std::string::npos ||
8929
0
                        info.name.find(" per second") != std::string::npos
8930
0
                    ? "scale_per_time"
8931
0
                    : "scale";
8932
0
        } else {
8933
0
            info.category = raw_category;
8934
0
        }
8935
0
        info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
8936
0
        info.projShortName = row[5];
8937
0
        info.deprecated = row[6] == "1";
8938
0
        res.emplace_back(info);
8939
0
    }
8940
0
    return res;
8941
0
}
8942
8943
// ---------------------------------------------------------------------------
8944
8945
/** \brief Return the list of celestial bodies.
8946
 * @throw FactoryException in case of error.
8947
 *
8948
 * @since 8.1
8949
 */
8950
std::list<AuthorityFactory::CelestialBodyInfo>
8951
0
AuthorityFactory::getCelestialBodyList() const {
8952
0
    std::string sql = "SELECT auth_name, name FROM celestial_body";
8953
0
    ListOfParams params;
8954
0
    if (d->hasAuthorityRestriction()) {
8955
0
        sql += " WHERE auth_name = ?";
8956
0
        params.emplace_back(d->authority());
8957
0
    }
8958
0
    sql += " ORDER BY auth_name, name";
8959
8960
0
    auto sqlRes = d->run(sql, params);
8961
0
    std::list<AuthorityFactory::CelestialBodyInfo> res;
8962
0
    for (const auto &row : sqlRes) {
8963
0
        AuthorityFactory::CelestialBodyInfo info;
8964
0
        info.authName = row[0];
8965
0
        info.name = row[1];
8966
0
        res.emplace_back(info);
8967
0
    }
8968
0
    return res;
8969
0
}
8970
8971
// ---------------------------------------------------------------------------
8972
8973
/** \brief Gets the official name from a possibly alias name.
8974
 *
8975
 * @param aliasedName Alias name.
8976
 * @param tableName Table name/category. Can help in case of ambiguities.
8977
 * Or empty otherwise.
8978
 * @param source Source of the alias. Can help in case of ambiguities.
8979
 * Or empty otherwise.
8980
 * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
8981
 * the alt_name column of the alias_name table should be done with using
8982
 * metadata::Identifier::isEquivalentName() rather than strict string
8983
 * comparison;
8984
 * @param outTableName Table name in which the official name has been found.
8985
 * @param outAuthName Authority name of the official name that has been found.
8986
 * @param outCode Code of the official name that has been found.
8987
 * @return official name (or empty if not found).
8988
 * @throw FactoryException in case of error.
8989
 */
8990
std::string AuthorityFactory::getOfficialNameFromAlias(
8991
    const std::string &aliasedName, const std::string &tableName,
8992
    const std::string &source, bool tryEquivalentNameSpelling,
8993
    std::string &outTableName, std::string &outAuthName,
8994
6.81k
    std::string &outCode) const {
8995
8996
6.81k
    if (tryEquivalentNameSpelling) {
8997
0
        std::string sql(
8998
0
            "SELECT table_name, auth_name, code, alt_name FROM alias_name");
8999
0
        ListOfParams params;
9000
0
        if (!tableName.empty()) {
9001
0
            sql += " WHERE table_name = ?";
9002
0
            params.push_back(tableName);
9003
0
        }
9004
0
        if (!source.empty()) {
9005
0
            if (!tableName.empty()) {
9006
0
                sql += " AND ";
9007
0
            } else {
9008
0
                sql += " WHERE ";
9009
0
            }
9010
0
            sql += "source = ?";
9011
0
            params.push_back(source);
9012
0
        }
9013
0
        auto res = d->run(sql, params);
9014
0
        if (res.empty()) {
9015
0
            return std::string();
9016
0
        }
9017
0
        for (const auto &row : res) {
9018
0
            const auto &alt_name = row[3];
9019
0
            if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
9020
0
                                                       aliasedName.c_str())) {
9021
0
                outTableName = row[0];
9022
0
                outAuthName = row[1];
9023
0
                outCode = row[2];
9024
0
                sql = "SELECT name FROM \"";
9025
0
                sql += replaceAll(outTableName, "\"", "\"\"");
9026
0
                sql += "\" WHERE auth_name = ? AND code = ?";
9027
0
                res = d->run(sql, {outAuthName, outCode});
9028
0
                if (res.empty()) { // shouldn't happen normally
9029
0
                    return std::string();
9030
0
                }
9031
0
                return res.front()[0];
9032
0
            }
9033
0
        }
9034
0
        return std::string();
9035
6.81k
    } else {
9036
6.81k
        std::string sql(
9037
6.81k
            "SELECT table_name, auth_name, code FROM alias_name WHERE "
9038
6.81k
            "alt_name = ?");
9039
6.81k
        ListOfParams params{aliasedName};
9040
6.81k
        if (!tableName.empty()) {
9041
6.81k
            sql += " AND table_name = ?";
9042
6.81k
            params.push_back(tableName);
9043
6.81k
        }
9044
6.81k
        if (!source.empty()) {
9045
6.81k
            if (source == "ESRI") {
9046
6.81k
                sql += " AND source IN ('ESRI', 'ESRI_OLD')";
9047
6.81k
            } else {
9048
0
                sql += " AND source = ?";
9049
0
                params.push_back(source);
9050
0
            }
9051
6.81k
        }
9052
6.81k
        auto res = d->run(sql, params);
9053
6.81k
        if (res.empty()) {
9054
6.56k
            return std::string();
9055
6.56k
        }
9056
9057
252
        params.clear();
9058
252
        sql.clear();
9059
252
        bool first = true;
9060
252
        for (const auto &row : res) {
9061
252
            if (!first)
9062
0
                sql += " UNION ALL ";
9063
252
            first = false;
9064
252
            outTableName = row[0];
9065
252
            outAuthName = row[1];
9066
252
            outCode = row[2];
9067
252
            sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
9068
252
                   "FROM \"";
9069
252
            sql += replaceAll(outTableName, "\"", "\"\"");
9070
252
            sql += "\" WHERE auth_name = ? AND code = ?";
9071
252
            params.emplace_back(outTableName);
9072
252
            params.emplace_back(outAuthName);
9073
252
            params.emplace_back(outCode);
9074
252
        }
9075
252
        sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
9076
252
              ") x ORDER BY deprecated LIMIT 1";
9077
252
        res = d->run(sql, params);
9078
252
        if (res.empty()) { // shouldn't happen normally
9079
0
            return std::string();
9080
0
        }
9081
252
        const auto &row = res.front();
9082
252
        outTableName = row[1];
9083
252
        outAuthName = row[2];
9084
252
        outCode = row[3];
9085
252
        return row[0];
9086
252
    }
9087
6.81k
}
9088
9089
// ---------------------------------------------------------------------------
9090
9091
/** \brief Return a list of objects, identified by their name
9092
 *
9093
 * @param searchedName Searched name. Must be at least 2 character long.
9094
 * @param allowedObjectTypes List of object types into which to search. If
9095
 * empty, all object types will be searched.
9096
 * @param approximateMatch Whether approximate name identification is allowed.
9097
 * @param limitResultCount Maximum number of results to return.
9098
 * Or 0 for unlimited.
9099
 * @return list of matched objects.
9100
 * @throw FactoryException in case of error.
9101
 */
9102
std::list<common::IdentifiedObjectNNPtr>
9103
AuthorityFactory::createObjectsFromName(
9104
    const std::string &searchedName,
9105
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9106
15.3k
    size_t limitResultCount) const {
9107
15.3k
    std::list<common::IdentifiedObjectNNPtr> res;
9108
15.3k
    const auto resTmp(createObjectsFromNameEx(
9109
15.3k
        searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
9110
15.3k
    for (const auto &pair : resTmp) {
9111
11.1k
        res.emplace_back(pair.first);
9112
11.1k
    }
9113
15.3k
    return res;
9114
15.3k
}
9115
9116
// ---------------------------------------------------------------------------
9117
9118
//! @cond Doxygen_Suppress
9119
9120
/** \brief Return a list of objects, identifier by their name, with the name
9121
 * on which the match occurred.
9122
 *
9123
 * The name on which the match occurred might be different from the object name,
9124
 * if the match has been done on an alias name of that object.
9125
 *
9126
 * @param searchedName Searched name. Must be at least 2 character long.
9127
 * @param allowedObjectTypes List of object types into which to search. If
9128
 * empty, all object types will be searched.
9129
 * @param approximateMatch Whether approximate name identification is allowed.
9130
 * @param limitResultCount Maximum number of results to return.
9131
 * Or 0 for unlimited.
9132
 * @return list of matched objects.
9133
 * @throw FactoryException in case of error.
9134
 */
9135
std::list<AuthorityFactory::PairObjectName>
9136
AuthorityFactory::createObjectsFromNameEx(
9137
    const std::string &searchedName,
9138
    const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
9139
15.3k
    size_t limitResultCount) const {
9140
15.3k
    std::string searchedNameWithoutDeprecated(searchedName);
9141
15.3k
    bool deprecated = false;
9142
15.3k
    if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
9143
4
        deprecated = true;
9144
4
        searchedNameWithoutDeprecated.resize(
9145
4
            searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
9146
4
    }
9147
9148
15.3k
    const std::string canonicalizedSearchedName(
9149
15.3k
        metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
9150
15.3k
    if (canonicalizedSearchedName.size() <= 1) {
9151
57
        return {};
9152
57
    }
9153
9154
15.2k
    std::string sql(
9155
15.2k
        "SELECT table_name, auth_name, code, name, deprecated, is_alias "
9156
15.2k
        "FROM (");
9157
9158
15.2k
    const auto getTableAndTypeConstraints = [&allowedObjectTypes,
9159
15.2k
                                             &searchedName]() {
9160
15.2k
        typedef std::pair<std::string, std::string> TableType;
9161
15.2k
        std::list<TableType> res;
9162
        // Hide ESRI D_ vertical datums
9163
15.2k
        const bool startsWithDUnderscore = starts_with(searchedName, "D_");
9164
15.2k
        if (allowedObjectTypes.empty()) {
9165
0
            for (const auto &tableName :
9166
0
                 {"prime_meridian", "ellipsoid", "geodetic_datum",
9167
0
                  "vertical_datum", "engineering_datum", "geodetic_crs",
9168
0
                  "projected_crs", "vertical_crs", "compound_crs",
9169
0
                  "engineering_crs", "conversion", "helmert_transformation",
9170
0
                  "grid_transformation", "other_transformation",
9171
0
                  "concatenated_operation"}) {
9172
0
                if (!(startsWithDUnderscore &&
9173
0
                      strcmp(tableName, "vertical_datum") == 0)) {
9174
0
                    res.emplace_back(TableType(tableName, std::string()));
9175
0
                }
9176
0
            }
9177
15.2k
        } else {
9178
25.4k
            for (const auto type : allowedObjectTypes) {
9179
25.4k
                switch (type) {
9180
0
                case ObjectType::PRIME_MERIDIAN:
9181
0
                    res.emplace_back(
9182
0
                        TableType("prime_meridian", std::string()));
9183
0
                    break;
9184
8.63k
                case ObjectType::ELLIPSOID:
9185
8.63k
                    res.emplace_back(TableType("ellipsoid", std::string()));
9186
8.63k
                    break;
9187
3.38k
                case ObjectType::DATUM:
9188
3.38k
                    res.emplace_back(
9189
3.38k
                        TableType("geodetic_datum", std::string()));
9190
3.38k
                    if (!startsWithDUnderscore) {
9191
3.37k
                        res.emplace_back(
9192
3.37k
                            TableType("vertical_datum", std::string()));
9193
3.37k
                        res.emplace_back(
9194
3.37k
                            TableType("engineering_datum", std::string()));
9195
3.37k
                    }
9196
3.38k
                    break;
9197
2.33k
                case ObjectType::GEODETIC_REFERENCE_FRAME:
9198
2.33k
                    res.emplace_back(
9199
2.33k
                        TableType("geodetic_datum", std::string()));
9200
2.33k
                    break;
9201
0
                case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
9202
0
                    res.emplace_back(
9203
0
                        TableType("geodetic_datum", "frame_reference_epoch"));
9204
0
                    break;
9205
0
                case ObjectType::VERTICAL_REFERENCE_FRAME:
9206
0
                    res.emplace_back(
9207
0
                        TableType("vertical_datum", std::string()));
9208
0
                    break;
9209
0
                case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
9210
0
                    res.emplace_back(
9211
0
                        TableType("vertical_datum", "frame_reference_epoch"));
9212
0
                    break;
9213
0
                case ObjectType::ENGINEERING_DATUM:
9214
0
                    res.emplace_back(
9215
0
                        TableType("engineering_datum", std::string()));
9216
0
                    break;
9217
4.22k
                case ObjectType::CRS:
9218
4.22k
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9219
4.22k
                    res.emplace_back(TableType("projected_crs", std::string()));
9220
4.22k
                    res.emplace_back(TableType("vertical_crs", std::string()));
9221
4.22k
                    res.emplace_back(TableType("compound_crs", std::string()));
9222
4.22k
                    res.emplace_back(
9223
4.22k
                        TableType("engineering_crs", std::string()));
9224
4.22k
                    break;
9225
0
                case ObjectType::GEODETIC_CRS:
9226
0
                    res.emplace_back(TableType("geodetic_crs", std::string()));
9227
0
                    break;
9228
0
                case ObjectType::GEOCENTRIC_CRS:
9229
0
                    res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
9230
0
                    break;
9231
0
                case ObjectType::GEOGRAPHIC_CRS:
9232
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9233
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9234
0
                    break;
9235
0
                case ObjectType::GEOGRAPHIC_2D_CRS:
9236
0
                    res.emplace_back(TableType("geodetic_crs", GEOG_2D));
9237
0
                    break;
9238
50
                case ObjectType::GEOGRAPHIC_3D_CRS:
9239
50
                    res.emplace_back(TableType("geodetic_crs", GEOG_3D));
9240
50
                    break;
9241
0
                case ObjectType::PROJECTED_CRS:
9242
0
                    res.emplace_back(TableType("projected_crs", std::string()));
9243
0
                    break;
9244
0
                case ObjectType::VERTICAL_CRS:
9245
0
                    res.emplace_back(TableType("vertical_crs", std::string()));
9246
0
                    break;
9247
0
                case ObjectType::COMPOUND_CRS:
9248
0
                    res.emplace_back(TableType("compound_crs", std::string()));
9249
0
                    break;
9250
0
                case ObjectType::ENGINEERING_CRS:
9251
0
                    res.emplace_back(
9252
0
                        TableType("engineering_crs", std::string()));
9253
0
                    break;
9254
3.38k
                case ObjectType::COORDINATE_OPERATION:
9255
3.38k
                    res.emplace_back(TableType("conversion", std::string()));
9256
3.38k
                    res.emplace_back(
9257
3.38k
                        TableType("helmert_transformation", std::string()));
9258
3.38k
                    res.emplace_back(
9259
3.38k
                        TableType("grid_transformation", std::string()));
9260
3.38k
                    res.emplace_back(
9261
3.38k
                        TableType("other_transformation", std::string()));
9262
3.38k
                    res.emplace_back(
9263
3.38k
                        TableType("concatenated_operation", std::string()));
9264
3.38k
                    break;
9265
0
                case ObjectType::CONVERSION:
9266
0
                    res.emplace_back(TableType("conversion", std::string()));
9267
0
                    break;
9268
0
                case ObjectType::TRANSFORMATION:
9269
0
                    res.emplace_back(
9270
0
                        TableType("helmert_transformation", std::string()));
9271
0
                    res.emplace_back(
9272
0
                        TableType("grid_transformation", std::string()));
9273
0
                    res.emplace_back(
9274
0
                        TableType("other_transformation", std::string()));
9275
0
                    break;
9276
0
                case ObjectType::CONCATENATED_OPERATION:
9277
0
                    res.emplace_back(
9278
0
                        TableType("concatenated_operation", std::string()));
9279
0
                    break;
9280
3.38k
                case ObjectType::DATUM_ENSEMBLE:
9281
3.38k
                    res.emplace_back(TableType("geodetic_datum", "ensemble"));
9282
3.38k
                    res.emplace_back(TableType("vertical_datum", "ensemble"));
9283
3.38k
                    break;
9284
25.4k
                }
9285
25.4k
            }
9286
15.2k
        }
9287
15.2k
        return res;
9288
15.2k
    };
9289
9290
15.2k
    bool datumEnsembleAllowed = false;
9291
15.2k
    if (allowedObjectTypes.empty()) {
9292
0
        datumEnsembleAllowed = true;
9293
15.2k
    } else {
9294
22.0k
        for (const auto type : allowedObjectTypes) {
9295
22.0k
            if (type == ObjectType::DATUM_ENSEMBLE) {
9296
3.38k
                datumEnsembleAllowed = true;
9297
3.38k
                break;
9298
3.38k
            }
9299
22.0k
        }
9300
15.2k
    }
9301
9302
15.2k
    const auto listTableNameType = getTableAndTypeConstraints();
9303
15.2k
    bool first = true;
9304
15.2k
    ListOfParams params;
9305
65.9k
    for (const auto &tableNameTypePair : listTableNameType) {
9306
65.9k
        if (!first) {
9307
50.7k
            sql += " UNION ";
9308
50.7k
        }
9309
65.9k
        first = false;
9310
65.9k
        sql += "SELECT '";
9311
65.9k
        sql += tableNameTypePair.first;
9312
65.9k
        sql += "' AS table_name, auth_name, code, name, deprecated, "
9313
65.9k
               "0 AS is_alias FROM ";
9314
65.9k
        sql += tableNameTypePair.first;
9315
65.9k
        sql += " WHERE 1 = 1 ";
9316
65.9k
        if (!tableNameTypePair.second.empty()) {
9317
6.82k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9318
0
                sql += "AND frame_reference_epoch IS NOT NULL ";
9319
6.82k
            } else if (tableNameTypePair.second == "ensemble") {
9320
6.77k
                sql += "AND ensemble_accuracy IS NOT NULL ";
9321
6.77k
            } else {
9322
50
                sql += "AND type = '";
9323
50
                sql += tableNameTypePair.second;
9324
50
                sql += "' ";
9325
50
            }
9326
6.82k
        }
9327
65.9k
        if (deprecated) {
9328
32
            sql += "AND deprecated = 1 ";
9329
32
        }
9330
65.9k
        if (!approximateMatch) {
9331
33.9k
            sql += "AND name = ? COLLATE NOCASE ";
9332
33.9k
            params.push_back(searchedNameWithoutDeprecated);
9333
33.9k
        }
9334
65.9k
        if (d->hasAuthorityRestriction()) {
9335
50
            sql += "AND auth_name = ? ";
9336
50
            params.emplace_back(d->authority());
9337
50
        }
9338
9339
65.9k
        sql += " UNION SELECT '";
9340
65.9k
        sql += tableNameTypePair.first;
9341
65.9k
        sql += "' AS table_name, "
9342
65.9k
               "ov.auth_name AS auth_name, "
9343
65.9k
               "ov.code AS code, a.alt_name AS name, "
9344
65.9k
               "ov.deprecated AS deprecated, 1 as is_alias FROM ";
9345
65.9k
        sql += tableNameTypePair.first;
9346
65.9k
        sql += " ov "
9347
65.9k
               "JOIN alias_name a ON "
9348
65.9k
               "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
9349
65.9k
               "a.table_name = '";
9350
65.9k
        sql += tableNameTypePair.first;
9351
65.9k
        sql += "' ";
9352
65.9k
        if (!tableNameTypePair.second.empty()) {
9353
6.82k
            if (tableNameTypePair.second == "frame_reference_epoch") {
9354
0
                sql += "AND ov.frame_reference_epoch IS NOT NULL ";
9355
6.82k
            } else if (tableNameTypePair.second == "ensemble") {
9356
6.77k
                sql += "AND ov.ensemble_accuracy IS NOT NULL ";
9357
6.77k
            } else {
9358
50
                sql += "AND ov.type = '";
9359
50
                sql += tableNameTypePair.second;
9360
50
                sql += "' ";
9361
50
            }
9362
6.82k
        }
9363
65.9k
        if (deprecated) {
9364
32
            sql += "AND ov.deprecated = 1 ";
9365
32
        }
9366
65.9k
        if (!approximateMatch) {
9367
33.9k
            sql += "AND a.alt_name = ? COLLATE NOCASE ";
9368
33.9k
            params.push_back(searchedNameWithoutDeprecated);
9369
33.9k
        }
9370
65.9k
        if (d->hasAuthorityRestriction()) {
9371
50
            sql += "AND ov.auth_name = ? ";
9372
50
            params.emplace_back(d->authority());
9373
50
        }
9374
65.9k
    }
9375
9376
15.2k
    sql += ") ORDER BY deprecated, is_alias, length(name), name";
9377
15.2k
    if (limitResultCount > 0 &&
9378
15.2k
        limitResultCount <
9379
15.2k
            static_cast<size_t>(std::numeric_limits<int>::max()) &&
9380
15.2k
        !approximateMatch) {
9381
4.25k
        sql += " LIMIT ";
9382
4.25k
        sql += toString(static_cast<int>(limitResultCount));
9383
4.25k
    }
9384
9385
15.2k
    std::list<PairObjectName> res;
9386
15.2k
    std::set<std::pair<std::string, std::string>> setIdentified;
9387
9388
    // Querying geodetic datum is a super hot path when importing from WKT1
9389
    // so cache results.
9390
15.2k
    if (allowedObjectTypes.size() == 1 &&
9391
15.2k
        allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
9392
15.2k
        approximateMatch && d->authority().empty()) {
9393
2.33k
        auto &mapCanonicalizeGRFName =
9394
2.33k
            d->context()->getPrivate()->getMapCanonicalizeGRFName();
9395
2.33k
        if (mapCanonicalizeGRFName.empty()) {
9396
1
            auto sqlRes = d->run(sql, params);
9397
2.40k
            for (const auto &row : sqlRes) {
9398
2.40k
                const auto &name = row[3];
9399
2.40k
                const auto &deprecatedStr = row[4];
9400
2.40k
                const auto canonicalizedName(
9401
2.40k
                    metadata::Identifier::canonicalizeName(name));
9402
2.40k
                auto &v = mapCanonicalizeGRFName[canonicalizedName];
9403
2.40k
                if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
9404
2.31k
                    v.push_back(row);
9405
2.31k
                }
9406
2.40k
            }
9407
1
        }
9408
2.33k
        auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
9409
2.33k
        if (iter != mapCanonicalizeGRFName.end()) {
9410
80
            const auto &listOfRow = iter->second;
9411
80
            for (const auto &row : listOfRow) {
9412
80
                const auto &auth_name = row[1];
9413
80
                const auto &code = row[2];
9414
80
                auto key = std::pair<std::string, std::string>(auth_name, code);
9415
80
                if (setIdentified.find(key) != setIdentified.end()) {
9416
0
                    continue;
9417
0
                }
9418
80
                setIdentified.insert(std::move(key));
9419
80
                auto factory = d->createFactory(auth_name);
9420
80
                const auto &name = row[3];
9421
80
                res.emplace_back(
9422
80
                    PairObjectName(factory->createGeodeticDatum(code), name));
9423
80
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9424
80
                    break;
9425
80
                }
9426
80
            }
9427
2.25k
        } else {
9428
3.69M
            for (const auto &pair : mapCanonicalizeGRFName) {
9429
3.69M
                const auto &listOfRow = pair.second;
9430
4.03M
                for (const auto &row : listOfRow) {
9431
4.03M
                    const auto &name = row[3];
9432
4.03M
                    bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9433
4.03M
                                 std::string::npos;
9434
4.03M
                    if (!match) {
9435
4.03M
                        const auto &canonicalizedName(pair.first);
9436
4.03M
                        match = ci_find(canonicalizedName,
9437
4.03M
                                        canonicalizedSearchedName) !=
9438
4.03M
                                std::string::npos;
9439
4.03M
                    }
9440
4.03M
                    if (!match) {
9441
4.03M
                        continue;
9442
4.03M
                    }
9443
9444
760
                    const auto &auth_name = row[1];
9445
760
                    const auto &code = row[2];
9446
760
                    auto key =
9447
760
                        std::pair<std::string, std::string>(auth_name, code);
9448
760
                    if (setIdentified.find(key) != setIdentified.end()) {
9449
0
                        continue;
9450
0
                    }
9451
760
                    setIdentified.insert(std::move(key));
9452
760
                    auto factory = d->createFactory(auth_name);
9453
760
                    res.emplace_back(PairObjectName(
9454
760
                        factory->createGeodeticDatum(code), name));
9455
760
                    if (limitResultCount > 0 &&
9456
760
                        res.size() == limitResultCount) {
9457
760
                        break;
9458
760
                    }
9459
760
                }
9460
3.69M
                if (limitResultCount > 0 && res.size() == limitResultCount) {
9461
760
                    break;
9462
760
                }
9463
3.69M
            }
9464
2.25k
        }
9465
12.9k
    } else {
9466
12.9k
        auto sqlRes = d->run(sql, params);
9467
12.9k
        bool isFirst = true;
9468
12.9k
        bool firstIsDeprecated = false;
9469
12.9k
        size_t countExactMatch = 0;
9470
12.9k
        size_t countExactMatchOnAlias = 0;
9471
12.9k
        std::size_t hashCodeFirstMatch = 0;
9472
58.8M
        for (const auto &row : sqlRes) {
9473
58.8M
            const auto &name = row[3];
9474
58.8M
            if (approximateMatch) {
9475
58.8M
                bool match = ci_find(name, searchedNameWithoutDeprecated) !=
9476
58.8M
                             std::string::npos;
9477
58.8M
                if (!match) {
9478
58.8M
                    const auto canonicalizedName(
9479
58.8M
                        metadata::Identifier::canonicalizeName(name));
9480
58.8M
                    match =
9481
58.8M
                        ci_find(canonicalizedName, canonicalizedSearchedName) !=
9482
58.8M
                        std::string::npos;
9483
58.8M
                }
9484
58.8M
                if (!match) {
9485
58.8M
                    continue;
9486
58.8M
                }
9487
58.8M
            }
9488
10.7k
            const auto &table_name = row[0];
9489
10.7k
            const auto &auth_name = row[1];
9490
10.7k
            const auto &code = row[2];
9491
10.7k
            auto key = std::pair<std::string, std::string>(auth_name, code);
9492
10.7k
            if (setIdentified.find(key) != setIdentified.end()) {
9493
364
                continue;
9494
364
            }
9495
10.3k
            setIdentified.insert(std::move(key));
9496
10.3k
            const auto &deprecatedStr = row[4];
9497
10.3k
            if (isFirst) {
9498
1.29k
                firstIsDeprecated = deprecatedStr == "1";
9499
1.29k
                isFirst = false;
9500
1.29k
            }
9501
10.3k
            if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
9502
89
                break;
9503
89
            }
9504
10.3k
            auto factory = d->createFactory(auth_name);
9505
10.3k
            auto getObject = [&factory, datumEnsembleAllowed](
9506
10.3k
                                 const std::string &l_table_name,
9507
10.3k
                                 const std::string &l_code)
9508
10.3k
                -> common::IdentifiedObjectNNPtr {
9509
10.3k
                if (l_table_name == "prime_meridian") {
9510
0
                    return factory->createPrimeMeridian(l_code);
9511
10.3k
                } else if (l_table_name == "ellipsoid") {
9512
64
                    return factory->createEllipsoid(l_code);
9513
10.2k
                } else if (l_table_name == "geodetic_datum") {
9514
188
                    if (datumEnsembleAllowed) {
9515
188
                        datum::GeodeticReferenceFramePtr datum;
9516
188
                        datum::DatumEnsemblePtr datumEnsemble;
9517
188
                        constexpr bool turnEnsembleAsDatum = false;
9518
188
                        factory->createGeodeticDatumOrEnsemble(
9519
188
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9520
188
                        if (datum) {
9521
188
                            return NN_NO_CHECK(datum);
9522
188
                        }
9523
0
                        assert(datumEnsemble);
9524
0
                        return NN_NO_CHECK(datumEnsemble);
9525
188
                    }
9526
0
                    return factory->createGeodeticDatum(l_code);
9527
10.0k
                } else if (l_table_name == "vertical_datum") {
9528
81
                    if (datumEnsembleAllowed) {
9529
81
                        datum::VerticalReferenceFramePtr datum;
9530
81
                        datum::DatumEnsemblePtr datumEnsemble;
9531
81
                        constexpr bool turnEnsembleAsDatum = false;
9532
81
                        factory->createVerticalDatumOrEnsemble(
9533
81
                            l_code, datum, datumEnsemble, turnEnsembleAsDatum);
9534
81
                        if (datum) {
9535
80
                            return NN_NO_CHECK(datum);
9536
80
                        }
9537
1
                        assert(datumEnsemble);
9538
1
                        return NN_NO_CHECK(datumEnsemble);
9539
81
                    }
9540
0
                    return factory->createVerticalDatum(l_code);
9541
9.97k
                } else if (l_table_name == "engineering_datum") {
9542
2
                    return factory->createEngineeringDatum(l_code);
9543
9.97k
                } else if (l_table_name == "geodetic_crs") {
9544
4.60k
                    return factory->createGeodeticCRS(l_code);
9545
5.37k
                } else if (l_table_name == "projected_crs") {
9546
2.73k
                    return factory->createProjectedCRS(l_code);
9547
2.73k
                } else if (l_table_name == "vertical_crs") {
9548
990
                    return factory->createVerticalCRS(l_code);
9549
1.64k
                } else if (l_table_name == "compound_crs") {
9550
266
                    return factory->createCompoundCRS(l_code);
9551
1.37k
                } else if (l_table_name == "engineering_crs") {
9552
15
                    return factory->createEngineeringCRS(l_code);
9553
1.36k
                } else if (l_table_name == "conversion") {
9554
261
                    return factory->createConversion(l_code);
9555
1.10k
                } else if (l_table_name == "grid_transformation" ||
9556
1.10k
                           l_table_name == "helmert_transformation" ||
9557
1.10k
                           l_table_name == "other_transformation" ||
9558
1.10k
                           l_table_name == "concatenated_operation") {
9559
1.10k
                    return factory->createCoordinateOperation(l_code, true);
9560
1.10k
                }
9561
0
                throw std::runtime_error("Unsupported table_name");
9562
10.3k
            };
9563
10.3k
            const auto obj = getObject(table_name, code);
9564
10.3k
            if (metadata::Identifier::isEquivalentName(
9565
10.3k
                    obj->nameStr().c_str(), searchedName.c_str(), false)) {
9566
20
                countExactMatch++;
9567
10.2k
            } else if (metadata::Identifier::isEquivalentName(
9568
10.2k
                           name.c_str(), searchedName.c_str(), false)) {
9569
28
                countExactMatchOnAlias++;
9570
28
            }
9571
9572
10.3k
            const auto objPtr = obj.get();
9573
10.3k
            if (res.empty()) {
9574
1.29k
                hashCodeFirstMatch = typeid(*objPtr).hash_code();
9575
9.01k
            } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
9576
7.93k
                hashCodeFirstMatch = 0;
9577
7.93k
            }
9578
9579
10.3k
            res.emplace_back(PairObjectName(obj, name));
9580
10.3k
            if (limitResultCount > 0 && res.size() == limitResultCount) {
9581
977
                break;
9582
977
            }
9583
10.3k
        }
9584
9585
        // If we found several objects that are an exact match, and all objects
9586
        // have the same type, and we are not in approximate mode, only keep the
9587
        // objects with the exact name match.
9588
12.9k
        if ((countExactMatch + countExactMatchOnAlias) >= 1 &&
9589
12.9k
            hashCodeFirstMatch != 0 && !approximateMatch) {
9590
13
            std::list<PairObjectName> resTmp;
9591
13
            bool biggerDifferencesAllowed = (countExactMatch == 0);
9592
13
            for (const auto &pair : res) {
9593
13
                if (metadata::Identifier::isEquivalentName(
9594
13
                        pair.first->nameStr().c_str(), searchedName.c_str(),
9595
13
                        biggerDifferencesAllowed) ||
9596
13
                    (countExactMatch == 0 &&
9597
12
                     metadata::Identifier::isEquivalentName(
9598
12
                         pair.second.c_str(), searchedName.c_str(),
9599
13
                         biggerDifferencesAllowed))) {
9600
13
                    resTmp.emplace_back(pair);
9601
13
                }
9602
13
            }
9603
13
            res = std::move(resTmp);
9604
13
        }
9605
12.9k
    }
9606
9607
16.4k
    auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
9608
16.4k
        const auto &aName = a.first->nameStr();
9609
16.4k
        const auto &bName = b.first->nameStr();
9610
9611
16.4k
        if (aName.size() < bName.size()) {
9612
214
            return true;
9613
214
        }
9614
16.2k
        if (aName.size() > bName.size()) {
9615
8.22k
            return false;
9616
8.22k
        }
9617
9618
7.99k
        const auto &aIds = a.first->identifiers();
9619
7.99k
        const auto &bIds = b.first->identifiers();
9620
7.99k
        if (aIds.size() < bIds.size()) {
9621
0
            return true;
9622
0
        }
9623
7.99k
        if (aIds.size() > bIds.size()) {
9624
0
            return false;
9625
0
        }
9626
7.99k
        for (size_t idx = 0; idx < aIds.size(); idx++) {
9627
7.99k
            const auto &aCodeSpace = *aIds[idx]->codeSpace();
9628
7.99k
            const auto &bCodeSpace = *bIds[idx]->codeSpace();
9629
7.99k
            const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
9630
7.99k
            if (codeSpaceComparison < 0) {
9631
958
                return true;
9632
958
            }
9633
7.03k
            if (codeSpaceComparison > 0) {
9634
1.05k
                return false;
9635
1.05k
            }
9636
5.98k
            const auto &aCode = aIds[idx]->code();
9637
5.98k
            const auto &bCode = bIds[idx]->code();
9638
5.98k
            const auto codeComparison = aCode.compare(bCode);
9639
5.98k
            if (codeComparison < 0) {
9640
1.18k
                return true;
9641
1.18k
            }
9642
4.80k
            if (codeComparison > 0) {
9643
4.80k
                return false;
9644
4.80k
            }
9645
4.80k
        }
9646
0
        return strcmp(typeid(a.first.get()).name(),
9647
0
                      typeid(b.first.get()).name()) < 0;
9648
7.99k
    };
9649
9650
15.2k
    res.sort(sortLambda);
9651
9652
15.2k
    return res;
9653
15.3k
}
9654
//! @endcond
9655
9656
// ---------------------------------------------------------------------------
9657
9658
/** \brief Return a list of area of use from their name
9659
 *
9660
 * @param name Searched name.
9661
 * @param approximateMatch Whether approximate name identification is allowed.
9662
 * @return list of (auth_name, code) of matched objects.
9663
 * @throw FactoryException in case of error.
9664
 */
9665
std::list<std::pair<std::string, std::string>>
9666
AuthorityFactory::listAreaOfUseFromName(const std::string &name,
9667
0
                                        bool approximateMatch) const {
9668
0
    std::string sql(
9669
0
        "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
9670
0
    ListOfParams params;
9671
0
    if (d->hasAuthorityRestriction()) {
9672
0
        sql += " auth_name = ? AND ";
9673
0
        params.emplace_back(d->authority());
9674
0
    }
9675
0
    sql += "name LIKE ?";
9676
0
    if (!approximateMatch) {
9677
0
        params.push_back(name);
9678
0
    } else {
9679
0
        params.push_back('%' + name + '%');
9680
0
    }
9681
0
    auto sqlRes = d->run(sql, params);
9682
0
    std::list<std::pair<std::string, std::string>> res;
9683
0
    for (const auto &row : sqlRes) {
9684
0
        res.emplace_back(row[0], row[1]);
9685
0
    }
9686
0
    return res;
9687
0
}
9688
9689
// ---------------------------------------------------------------------------
9690
9691
//! @cond Doxygen_Suppress
9692
std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
9693
0
    const datum::EllipsoidNNPtr &ellipsoid) const {
9694
0
    std::string sql(
9695
0
        "SELECT auth_name, code FROM ellipsoid WHERE "
9696
0
        "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
9697
0
        "((semi_minor_axis IS NOT NULL AND "
9698
0
        "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
9699
0
        "((inv_flattening IS NOT NULL AND "
9700
0
        "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
9701
0
    ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
9702
0
                        ellipsoid->computeSemiMinorAxis().getSIValue(),
9703
0
                        ellipsoid->computedInverseFlattening()};
9704
0
    auto sqlRes = d->run(sql, params);
9705
0
    std::list<datum::EllipsoidNNPtr> res;
9706
0
    for (const auto &row : sqlRes) {
9707
0
        const auto &auth_name = row[0];
9708
0
        const auto &code = row[1];
9709
0
        res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
9710
0
    }
9711
0
    return res;
9712
0
}
9713
//! @endcond
9714
9715
// ---------------------------------------------------------------------------
9716
9717
//! @cond Doxygen_Suppress
9718
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9719
    const std::string &datum_auth_name, const std::string &datum_code,
9720
27
    const std::string &geodetic_crs_type) const {
9721
27
    std::string sql(
9722
27
        "SELECT auth_name, code FROM geodetic_crs WHERE "
9723
27
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9724
27
    ListOfParams params{datum_auth_name, datum_code};
9725
27
    if (d->hasAuthorityRestriction()) {
9726
0
        sql += " AND auth_name = ?";
9727
0
        params.emplace_back(d->authority());
9728
0
    }
9729
27
    if (!geodetic_crs_type.empty()) {
9730
0
        sql += " AND type = ?";
9731
0
        params.emplace_back(geodetic_crs_type);
9732
0
    }
9733
27
    sql += " ORDER BY auth_name, code";
9734
27
    auto sqlRes = d->run(sql, params);
9735
27
    std::list<crs::GeodeticCRSNNPtr> res;
9736
85
    for (const auto &row : sqlRes) {
9737
85
        const auto &auth_name = row[0];
9738
85
        const auto &code = row[1];
9739
85
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9740
85
    }
9741
27
    return res;
9742
27
}
9743
//! @endcond
9744
9745
// ---------------------------------------------------------------------------
9746
9747
//! @cond Doxygen_Suppress
9748
std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
9749
    const datum::GeodeticReferenceFrameNNPtr &datum,
9750
    const std::string &preferredAuthName,
9751
30
    const std::string &geodetic_crs_type) const {
9752
30
    std::list<crs::GeodeticCRSNNPtr> candidates;
9753
30
    const auto &ids = datum->identifiers();
9754
30
    const auto &datumName = datum->nameStr();
9755
30
    if (!ids.empty()) {
9756
27
        for (const auto &id : ids) {
9757
27
            const auto &authName = *(id->codeSpace());
9758
27
            const auto &code = id->code();
9759
27
            if (!authName.empty()) {
9760
27
                const auto tmpFactory =
9761
27
                    (preferredAuthName == authName)
9762
27
                        ? create(databaseContext(), authName)
9763
27
                        : NN_NO_CHECK(d->getSharedFromThis());
9764
27
                auto l_candidates = tmpFactory->createGeodeticCRSFromDatum(
9765
27
                    authName, code, geodetic_crs_type);
9766
85
                for (const auto &candidate : l_candidates) {
9767
85
                    candidates.emplace_back(candidate);
9768
85
                }
9769
27
            }
9770
27
        }
9771
27
    } else if (datumName != "unknown" && datumName != "unnamed") {
9772
1
        auto matches = createObjectsFromName(
9773
1
            datumName,
9774
1
            {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, false,
9775
1
            2);
9776
1
        if (matches.size() == 1) {
9777
0
            const auto &match = matches.front();
9778
0
            if (datum->_isEquivalentTo(match.get(),
9779
0
                                       util::IComparable::Criterion::EQUIVALENT,
9780
0
                                       databaseContext().as_nullable()) &&
9781
0
                !match->identifiers().empty()) {
9782
0
                return createGeodeticCRSFromDatum(
9783
0
                    util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
9784
0
                        match),
9785
0
                    preferredAuthName, geodetic_crs_type);
9786
0
            }
9787
0
        }
9788
1
    }
9789
30
    return candidates;
9790
30
}
9791
//! @endcond
9792
9793
// ---------------------------------------------------------------------------
9794
9795
//! @cond Doxygen_Suppress
9796
std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
9797
0
    const std::string &datum_auth_name, const std::string &datum_code) const {
9798
0
    std::string sql(
9799
0
        "SELECT auth_name, code FROM vertical_crs WHERE "
9800
0
        "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
9801
0
    ListOfParams params{datum_auth_name, datum_code};
9802
0
    if (d->hasAuthorityRestriction()) {
9803
0
        sql += " AND auth_name = ?";
9804
0
        params.emplace_back(d->authority());
9805
0
    }
9806
0
    auto sqlRes = d->run(sql, params);
9807
0
    std::list<crs::VerticalCRSNNPtr> res;
9808
0
    for (const auto &row : sqlRes) {
9809
0
        const auto &auth_name = row[0];
9810
0
        const auto &code = row[1];
9811
0
        res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
9812
0
    }
9813
0
    return res;
9814
0
}
9815
//! @endcond
9816
9817
// ---------------------------------------------------------------------------
9818
9819
//! @cond Doxygen_Suppress
9820
std::list<crs::GeodeticCRSNNPtr>
9821
AuthorityFactory::createGeodeticCRSFromEllipsoid(
9822
    const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
9823
0
    const std::string &geodetic_crs_type) const {
9824
0
    std::string sql(
9825
0
        "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
9826
0
        "JOIN geodetic_datum ON "
9827
0
        "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
9828
0
        "geodetic_crs.datum_code = geodetic_datum.code WHERE "
9829
0
        "geodetic_datum.ellipsoid_auth_name = ? AND "
9830
0
        "geodetic_datum.ellipsoid_code = ? AND "
9831
0
        "geodetic_datum.deprecated = 0 AND "
9832
0
        "geodetic_crs.deprecated = 0");
9833
0
    ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
9834
0
    if (d->hasAuthorityRestriction()) {
9835
0
        sql += " AND geodetic_crs.auth_name = ?";
9836
0
        params.emplace_back(d->authority());
9837
0
    }
9838
0
    if (!geodetic_crs_type.empty()) {
9839
0
        sql += " AND geodetic_crs.type = ?";
9840
0
        params.emplace_back(geodetic_crs_type);
9841
0
    }
9842
0
    auto sqlRes = d->run(sql, params);
9843
0
    std::list<crs::GeodeticCRSNNPtr> res;
9844
0
    for (const auto &row : sqlRes) {
9845
0
        const auto &auth_name = row[0];
9846
0
        const auto &code = row[1];
9847
0
        res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
9848
0
    }
9849
0
    return res;
9850
0
}
9851
//! @endcond
9852
9853
// ---------------------------------------------------------------------------
9854
9855
//! @cond Doxygen_Suppress
9856
static std::string buildSqlLookForAuthNameCode(
9857
    const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
9858
0
    const char *prefixField) {
9859
0
    std::string sql("(");
9860
9861
0
    std::set<std::string> authorities;
9862
0
    for (const auto &crs : list) {
9863
0
        auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9864
0
        const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9865
0
                                   : crs.first->identifiers();
9866
0
        if (!ids.empty()) {
9867
0
            authorities.insert(*(ids[0]->codeSpace()));
9868
0
        }
9869
0
    }
9870
0
    bool firstAuth = true;
9871
0
    for (const auto &auth_name : authorities) {
9872
0
        if (!firstAuth) {
9873
0
            sql += " OR ";
9874
0
        }
9875
0
        firstAuth = false;
9876
0
        sql += "( ";
9877
0
        sql += prefixField;
9878
0
        sql += "auth_name = ? AND ";
9879
0
        sql += prefixField;
9880
0
        sql += "code IN (";
9881
0
        params.emplace_back(auth_name);
9882
0
        bool firstGeodCRSForAuth = true;
9883
0
        for (const auto &crs : list) {
9884
0
            auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
9885
0
            const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
9886
0
                                       : crs.first->identifiers();
9887
0
            if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
9888
0
                if (!firstGeodCRSForAuth) {
9889
0
                    sql += ',';
9890
0
                }
9891
0
                firstGeodCRSForAuth = false;
9892
0
                sql += '?';
9893
0
                params.emplace_back(ids[0]->code());
9894
0
            }
9895
0
        }
9896
0
        sql += "))";
9897
0
    }
9898
0
    sql += ')';
9899
0
    return sql;
9900
0
}
9901
//! @endcond
9902
9903
// ---------------------------------------------------------------------------
9904
9905
//! @cond Doxygen_Suppress
9906
std::list<crs::ProjectedCRSNNPtr>
9907
AuthorityFactory::createProjectedCRSFromExisting(
9908
0
    const crs::ProjectedCRSNNPtr &crs) const {
9909
0
    std::list<crs::ProjectedCRSNNPtr> res;
9910
9911
0
    const auto &conv = crs->derivingConversionRef();
9912
0
    const auto &method = conv->method();
9913
0
    const auto methodEPSGCode = method->getEPSGCode();
9914
0
    if (methodEPSGCode == 0) {
9915
0
        return res;
9916
0
    }
9917
9918
0
    auto lockedThisFactory(d->getSharedFromThis());
9919
0
    assert(lockedThisFactory);
9920
0
    const auto &baseCRS(crs->baseCRS());
9921
0
    auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
9922
0
    auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
9923
0
    if (geogCRS) {
9924
0
        const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
9925
0
        if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
9926
0
            axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
9927
0
            const auto &unit =
9928
0
                geogCRS->coordinateSystem()->axisList()[0]->unit();
9929
0
            auto otherOrderGeogCRS = crs::GeographicCRS::create(
9930
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
9931
0
                                        geogCRS->nameStr()),
9932
0
                geogCRS->datum(), geogCRS->datumEnsemble(),
9933
0
                axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
9934
0
                    ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
9935
0
                    : cs::EllipsoidalCS::createLongitudeLatitude(unit));
9936
0
            auto otherCandidatesGeodCRS =
9937
0
                otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
9938
0
            candidatesGeodCRS.insert(candidatesGeodCRS.end(),
9939
0
                                     otherCandidatesGeodCRS.begin(),
9940
0
                                     otherCandidatesGeodCRS.end());
9941
0
        }
9942
0
    }
9943
9944
0
    std::string sql("SELECT projected_crs.auth_name, projected_crs.code, "
9945
0
                    "projected_crs.name FROM projected_crs "
9946
0
                    "JOIN conversion_table conv ON "
9947
0
                    "projected_crs.conversion_auth_name = conv.auth_name AND "
9948
0
                    "projected_crs.conversion_code = conv.code WHERE "
9949
0
                    "projected_crs.deprecated = 0 AND ");
9950
0
    ListOfParams params;
9951
0
    if (!candidatesGeodCRS.empty()) {
9952
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
9953
0
                                           "projected_crs.geodetic_crs_");
9954
0
        sql += " AND ";
9955
0
    }
9956
0
    sql += "conv.method_auth_name = 'EPSG' AND "
9957
0
           "conv.method_code = ?";
9958
0
    params.emplace_back(toString(methodEPSGCode));
9959
0
    if (d->hasAuthorityRestriction()) {
9960
0
        sql += " AND projected_crs.auth_name = ?";
9961
0
        params.emplace_back(d->authority());
9962
0
    }
9963
9964
0
    int iParam = 0;
9965
0
    bool hasLat1stStd = false;
9966
0
    double lat1stStd = 0;
9967
0
    int iParamLat1stStd = 0;
9968
0
    bool hasLat2ndStd = false;
9969
0
    double lat2ndStd = 0;
9970
0
    int iParamLat2ndStd = 0;
9971
0
    for (const auto &genOpParamvalue : conv->parameterValues()) {
9972
0
        iParam++;
9973
0
        auto opParamvalue =
9974
0
            dynamic_cast<const operation::OperationParameterValue *>(
9975
0
                genOpParamvalue.get());
9976
0
        if (!opParamvalue) {
9977
0
            break;
9978
0
        }
9979
0
        const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
9980
0
        const auto &parameterValue = opParamvalue->parameterValue();
9981
0
        if (!(paramEPSGCode > 0 &&
9982
0
              parameterValue->type() ==
9983
0
                  operation::ParameterValue::Type::MEASURE)) {
9984
0
            break;
9985
0
        }
9986
0
        const auto &measure = parameterValue->value();
9987
0
        const auto &unit = measure.unit();
9988
0
        if (unit == common::UnitOfMeasure::DEGREE &&
9989
0
            baseCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
9990
0
            if (methodEPSGCode ==
9991
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
9992
                // Special case for standard parallels of LCC_2SP. See below
9993
0
                if (paramEPSGCode ==
9994
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
9995
0
                    hasLat1stStd = true;
9996
0
                    lat1stStd = measure.value();
9997
0
                    iParamLat1stStd = iParam;
9998
0
                    continue;
9999
0
                } else if (paramEPSGCode ==
10000
0
                           EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
10001
0
                    hasLat2ndStd = true;
10002
0
                    lat2ndStd = measure.value();
10003
0
                    iParamLat2ndStd = iParam;
10004
0
                    continue;
10005
0
                }
10006
0
            }
10007
0
            const auto iParamAsStr(toString(iParam));
10008
0
            sql += " AND conv.param";
10009
0
            sql += iParamAsStr;
10010
0
            sql += "_code = ? AND conv.param";
10011
0
            sql += iParamAsStr;
10012
0
            sql += "_auth_name = 'EPSG' AND conv.param";
10013
0
            sql += iParamAsStr;
10014
0
            sql += "_value BETWEEN ? AND ?";
10015
            // As angles might be expressed with the odd unit EPSG:9110
10016
            // "sexagesimal DMS", we have to provide a broad range
10017
0
            params.emplace_back(toString(paramEPSGCode));
10018
0
            params.emplace_back(measure.value() - 1);
10019
0
            params.emplace_back(measure.value() + 1);
10020
0
        }
10021
0
    }
10022
10023
    // Special case for standard parallels of LCC_2SP: they can be switched
10024
0
    if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
10025
0
        hasLat1stStd && hasLat2ndStd) {
10026
0
        const auto iParam1AsStr(toString(iParamLat1stStd));
10027
0
        const auto iParam2AsStr(toString(iParamLat2ndStd));
10028
0
        sql += " AND conv.param";
10029
0
        sql += iParam1AsStr;
10030
0
        sql += "_code = ? AND conv.param";
10031
0
        sql += iParam1AsStr;
10032
0
        sql += "_auth_name = 'EPSG' AND conv.param";
10033
0
        sql += iParam2AsStr;
10034
0
        sql += "_code = ? AND conv.param";
10035
0
        sql += iParam2AsStr;
10036
0
        sql += "_auth_name = 'EPSG' AND ((";
10037
0
        params.emplace_back(
10038
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
10039
0
        params.emplace_back(
10040
0
            toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
10041
0
        double val1 = lat1stStd;
10042
0
        double val2 = lat2ndStd;
10043
0
        for (int i = 0; i < 2; i++) {
10044
0
            if (i == 1) {
10045
0
                sql += ") OR (";
10046
0
                std::swap(val1, val2);
10047
0
            }
10048
0
            sql += "conv.param";
10049
0
            sql += iParam1AsStr;
10050
0
            sql += "_value BETWEEN ? AND ? AND conv.param";
10051
0
            sql += iParam2AsStr;
10052
0
            sql += "_value BETWEEN ? AND ?";
10053
0
            params.emplace_back(val1 - 1);
10054
0
            params.emplace_back(val1 + 1);
10055
0
            params.emplace_back(val2 - 1);
10056
0
            params.emplace_back(val2 + 1);
10057
0
        }
10058
0
        sql += "))";
10059
0
    }
10060
0
    auto sqlRes = d->run(sql, params);
10061
10062
0
    for (const auto &row : sqlRes) {
10063
0
        const auto &name = row[2];
10064
0
        if (metadata::Identifier::isEquivalentName(crs->nameStr().c_str(),
10065
0
                                                   name.c_str())) {
10066
0
            const auto &auth_name = row[0];
10067
0
            const auto &code = row[1];
10068
0
            res.emplace_back(
10069
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10070
0
        }
10071
0
    }
10072
0
    if (!res.empty()) {
10073
0
        return res;
10074
0
    }
10075
10076
0
    params.clear();
10077
10078
0
    sql = "SELECT auth_name, code FROM projected_crs WHERE "
10079
0
          "deprecated = 0 AND conversion_auth_name IS NULL AND ";
10080
0
    if (!candidatesGeodCRS.empty()) {
10081
0
        sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
10082
0
                                           "geodetic_crs_");
10083
0
        sql += " AND ";
10084
0
    }
10085
10086
0
    const auto escapeLikeStr = [](const std::string &str) {
10087
0
        return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
10088
0
                          "%", "\\%");
10089
0
    };
10090
10091
0
    const auto ellpsSemiMajorStr =
10092
0
        toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
10093
10094
0
    sql += "(text_definition LIKE ? ESCAPE '\\'";
10095
10096
    // WKT2 definition
10097
0
    {
10098
0
        std::string patternVal("%");
10099
10100
0
        patternVal += ',';
10101
0
        patternVal += ellpsSemiMajorStr;
10102
0
        patternVal += '%';
10103
10104
0
        patternVal += escapeLikeStr(method->nameStr());
10105
0
        patternVal += '%';
10106
10107
0
        params.emplace_back(patternVal);
10108
0
    }
10109
10110
0
    const auto *mapping = getMapping(method.get());
10111
0
    if (mapping && mapping->proj_name_main) {
10112
0
        sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
10113
10114
0
        std::string patternVal("%");
10115
0
        patternVal += "proj=";
10116
0
        patternVal += mapping->proj_name_main;
10117
0
        patternVal += '%';
10118
0
        params.emplace_back(patternVal);
10119
10120
        // could be a= or R=
10121
0
        patternVal = "%=";
10122
0
        patternVal += ellpsSemiMajorStr;
10123
0
        patternVal += '%';
10124
0
        params.emplace_back(patternVal);
10125
10126
0
        std::string projEllpsName;
10127
0
        std::string ellpsName;
10128
0
        if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
10129
0
                                                            ellpsName)) {
10130
0
            sql += " OR text_definition LIKE ?";
10131
            // Could be ellps= or datum=
10132
0
            patternVal = "%=";
10133
0
            patternVal += projEllpsName;
10134
0
            patternVal += '%';
10135
0
            params.emplace_back(patternVal);
10136
0
        }
10137
10138
0
        sql += "))";
10139
0
    }
10140
10141
    // WKT1_GDAL definition
10142
0
    const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
10143
0
    if (wkt1GDALMethodName) {
10144
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10145
0
        std::string patternVal("%");
10146
10147
0
        patternVal += ',';
10148
0
        patternVal += ellpsSemiMajorStr;
10149
0
        patternVal += '%';
10150
10151
0
        patternVal += escapeLikeStr(wkt1GDALMethodName);
10152
0
        patternVal += '%';
10153
10154
0
        params.emplace_back(patternVal);
10155
0
    }
10156
10157
    // WKT1_ESRI definition
10158
0
    const char *esriMethodName = conv->getESRIMethodName();
10159
0
    if (esriMethodName) {
10160
0
        sql += " OR text_definition LIKE ? ESCAPE '\\'";
10161
0
        std::string patternVal("%");
10162
10163
0
        patternVal += ',';
10164
0
        patternVal += ellpsSemiMajorStr;
10165
0
        patternVal += '%';
10166
10167
0
        patternVal += escapeLikeStr(esriMethodName);
10168
0
        patternVal += '%';
10169
10170
0
        auto fe =
10171
0
            &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
10172
0
        if (*fe == Measure()) {
10173
0
            fe = &conv->parameterValueMeasure(
10174
0
                EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
10175
0
        }
10176
0
        if (!(*fe == Measure())) {
10177
0
            patternVal += "PARAMETER[\"False\\_Easting\",";
10178
0
            patternVal +=
10179
0
                toString(fe->convertToUnit(
10180
0
                             crs->coordinateSystem()->axisList()[0]->unit()),
10181
0
                         10);
10182
0
            patternVal += '%';
10183
0
        }
10184
10185
0
        auto lat = &conv->parameterValueMeasure(
10186
0
            EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
10187
0
        if (*lat == Measure()) {
10188
0
            lat = &conv->parameterValueMeasure(
10189
0
                EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
10190
0
        }
10191
0
        if (!(*lat == Measure())) {
10192
0
            patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
10193
0
            const auto &angularUnit =
10194
0
                dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
10195
0
                    ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
10196
0
                    : UnitOfMeasure::DEGREE;
10197
0
            patternVal += toString(lat->convertToUnit(angularUnit), 10);
10198
0
            patternVal += '%';
10199
0
        }
10200
10201
0
        params.emplace_back(patternVal);
10202
0
    }
10203
0
    sql += ")";
10204
0
    if (d->hasAuthorityRestriction()) {
10205
0
        sql += " AND auth_name = ?";
10206
0
        params.emplace_back(d->authority());
10207
0
    }
10208
10209
0
    auto sqlRes2 = d->run(sql, params);
10210
10211
0
    if (sqlRes.size() <= 200) {
10212
0
        for (const auto &row : sqlRes) {
10213
0
            const auto &auth_name = row[0];
10214
0
            const auto &code = row[1];
10215
0
            res.emplace_back(
10216
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10217
0
        }
10218
0
    }
10219
0
    if (sqlRes2.size() <= 200) {
10220
0
        for (const auto &row : sqlRes2) {
10221
0
            const auto &auth_name = row[0];
10222
0
            const auto &code = row[1];
10223
0
            res.emplace_back(
10224
0
                d->createFactory(auth_name)->createProjectedCRS(code));
10225
0
        }
10226
0
    }
10227
10228
0
    return res;
10229
0
}
10230
10231
// ---------------------------------------------------------------------------
10232
10233
std::list<crs::CompoundCRSNNPtr>
10234
AuthorityFactory::createCompoundCRSFromExisting(
10235
0
    const crs::CompoundCRSNNPtr &crs) const {
10236
0
    std::list<crs::CompoundCRSNNPtr> res;
10237
10238
0
    auto lockedThisFactory(d->getSharedFromThis());
10239
0
    assert(lockedThisFactory);
10240
10241
0
    const auto &components = crs->componentReferenceSystems();
10242
0
    if (components.size() != 2) {
10243
0
        return res;
10244
0
    }
10245
0
    auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
10246
0
    auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
10247
0
    if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
10248
0
        return res;
10249
0
    }
10250
10251
0
    std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
10252
0
                    "deprecated = 0 AND ");
10253
0
    ListOfParams params;
10254
0
    bool addAnd = false;
10255
0
    if (!candidatesHorizCRS.empty()) {
10256
0
        sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
10257
0
                                           "horiz_crs_");
10258
0
        addAnd = true;
10259
0
    }
10260
0
    if (!candidatesVertCRS.empty()) {
10261
0
        if (addAnd) {
10262
0
            sql += " AND ";
10263
0
        }
10264
0
        sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
10265
0
                                           "vertical_crs_");
10266
0
        addAnd = true;
10267
0
    }
10268
0
    if (d->hasAuthorityRestriction()) {
10269
0
        if (addAnd) {
10270
0
            sql += " AND ";
10271
0
        }
10272
0
        sql += "auth_name = ?";
10273
0
        params.emplace_back(d->authority());
10274
0
    }
10275
10276
0
    auto sqlRes = d->run(sql, params);
10277
0
    for (const auto &row : sqlRes) {
10278
0
        const auto &auth_name = row[0];
10279
0
        const auto &code = row[1];
10280
0
        res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
10281
0
    }
10282
0
    return res;
10283
0
}
10284
10285
// ---------------------------------------------------------------------------
10286
10287
std::vector<operation::CoordinateOperationNNPtr>
10288
AuthorityFactory::getTransformationsForGeoid(
10289
0
    const std::string &geoidName, bool usePROJAlternativeGridNames) const {
10290
0
    std::vector<operation::CoordinateOperationNNPtr> res;
10291
10292
0
    const std::string sql("SELECT operation_auth_name, operation_code FROM "
10293
0
                          "geoid_model WHERE name = ?");
10294
0
    auto sqlRes = d->run(sql, {geoidName});
10295
0
    for (const auto &row : sqlRes) {
10296
0
        const auto &auth_name = row[0];
10297
0
        const auto &code = row[1];
10298
0
        res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
10299
0
            code, usePROJAlternativeGridNames));
10300
0
    }
10301
10302
0
    return res;
10303
0
}
10304
10305
// ---------------------------------------------------------------------------
10306
10307
std::vector<operation::PointMotionOperationNNPtr>
10308
AuthorityFactory::getPointMotionOperationsFor(
10309
30
    const crs::GeodeticCRSNNPtr &crs, bool usePROJAlternativeGridNames) const {
10310
30
    std::vector<operation::PointMotionOperationNNPtr> res;
10311
30
    const auto crsList =
10312
30
        createGeodeticCRSFromDatum(crs->datumNonNull(d->context()),
10313
30
                                   /* preferredAuthName = */ std::string(),
10314
30
                                   /* geodetic_crs_type = */ std::string());
10315
30
    if (crsList.empty())
10316
3
        return res;
10317
27
    std::string sql("SELECT auth_name, code FROM coordinate_operation_view "
10318
27
                    "WHERE source_crs_auth_name = target_crs_auth_name AND "
10319
27
                    "source_crs_code = target_crs_code AND deprecated = 0 AND "
10320
27
                    "(");
10321
27
    bool addOr = false;
10322
27
    ListOfParams params;
10323
85
    for (const auto &candidateCrs : crsList) {
10324
85
        if (addOr)
10325
58
            sql += " OR ";
10326
85
        addOr = true;
10327
85
        sql += "(source_crs_auth_name = ? AND source_crs_code = ?)";
10328
85
        const auto &ids = candidateCrs->identifiers();
10329
85
        params.emplace_back(*(ids[0]->codeSpace()));
10330
85
        params.emplace_back(ids[0]->code());
10331
85
    }
10332
27
    sql += ")";
10333
27
    if (d->hasAuthorityRestriction()) {
10334
0
        sql += " AND auth_name = ?";
10335
0
        params.emplace_back(d->authority());
10336
0
    }
10337
10338
27
    auto sqlRes = d->run(sql, params);
10339
27
    for (const auto &row : sqlRes) {
10340
0
        const auto &auth_name = row[0];
10341
0
        const auto &code = row[1];
10342
0
        auto pmo =
10343
0
            util::nn_dynamic_pointer_cast<operation::PointMotionOperation>(
10344
0
                d->createFactory(auth_name)->createCoordinateOperation(
10345
0
                    code, usePROJAlternativeGridNames));
10346
0
        if (pmo) {
10347
0
            res.emplace_back(NN_NO_CHECK(pmo));
10348
0
        }
10349
0
    }
10350
27
    return res;
10351
30
}
10352
10353
//! @endcond
10354
10355
// ---------------------------------------------------------------------------
10356
10357
//! @cond Doxygen_Suppress
10358
9.31k
FactoryException::FactoryException(const char *message) : Exception(message) {}
10359
10360
// ---------------------------------------------------------------------------
10361
10362
FactoryException::FactoryException(const std::string &message)
10363
2.20k
    : Exception(message) {}
10364
10365
// ---------------------------------------------------------------------------
10366
10367
11.5k
FactoryException::~FactoryException() = default;
10368
10369
// ---------------------------------------------------------------------------
10370
10371
0
FactoryException::FactoryException(const FactoryException &) = default;
10372
//! @endcond
10373
10374
// ---------------------------------------------------------------------------
10375
10376
//! @cond Doxygen_Suppress
10377
10378
struct NoSuchAuthorityCodeException::Private {
10379
    std::string authority_;
10380
    std::string code_;
10381
10382
    Private(const std::string &authority, const std::string &code)
10383
2.20k
        : authority_(authority), code_(code) {}
10384
};
10385
10386
// ---------------------------------------------------------------------------
10387
10388
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10389
    const std::string &message, const std::string &authority,
10390
    const std::string &code)
10391
2.20k
    : FactoryException(message), d(std::make_unique<Private>(authority, code)) {
10392
2.20k
}
10393
10394
// ---------------------------------------------------------------------------
10395
10396
2.20k
NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
10397
10398
// ---------------------------------------------------------------------------
10399
10400
NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
10401
    const NoSuchAuthorityCodeException &other)
10402
0
    : FactoryException(other), d(std::make_unique<Private>(*(other.d))) {}
10403
//! @endcond
10404
10405
// ---------------------------------------------------------------------------
10406
10407
/** \brief Returns authority name. */
10408
453
const std::string &NoSuchAuthorityCodeException::getAuthority() const {
10409
453
    return d->authority_;
10410
453
}
10411
10412
// ---------------------------------------------------------------------------
10413
10414
/** \brief Returns authority code. */
10415
453
const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
10416
453
    return d->code_;
10417
453
}
10418
10419
// ---------------------------------------------------------------------------
10420
10421
} // namespace io
10422
NS_PROJ_END
10423
10424
// ---------------------------------------------------------------------------
10425
10426
0
void pj_clear_sqlite_cache() { NS_PROJ::io::SQLiteHandleCache::get().clear(); }